Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1,2 +1,8 @@
# Copy this file to .env and add your actual API key
# Copy this file to .env and add your actual API keys

# Company LLM Provider (Primary) - RDSec Internal API
RDSEC_API_ENDPOINT=https://api.rdsec.trendmicro.com/prod/aiendpoint/v1
RDSEC_API_KEY=your-rdsec-api-key-here

# Legacy Anthropic API (Fallback)
ANTHROPIC_API_KEY=your-anthropic-api-key-here
161 changes: 161 additions & 0 deletions backend/ai_generator.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,167 @@
import anthropic
import openai
import tiktoken
from typing import List, Optional, Dict, Any

class OpenAIGenerator:
"""Handles interactions with company's OpenAI-compatible LLM API for generating responses"""

# Static system prompt to avoid rebuilding on each call
SYSTEM_PROMPT = """ You are an AI assistant specialized in course materials and educational content with access to a comprehensive search tool for course information.

Search Tool Usage:
- Use the search tool **only** for questions about specific course content or detailed educational materials
- **One search per query maximum**
- Synthesize search results into accurate, fact-based responses
- If search yields no results, state this clearly without offering alternatives

Response Protocol:
- **General knowledge questions**: Answer using existing knowledge without searching
- **Course-specific questions**: Search first, then answer
- **No meta-commentary**:
- Provide direct answers only — no reasoning process, search explanations, or question-type analysis
- Do not mention "based on the search results"


All responses must be:
1. **Brief, Concise and focused** - Get to the point quickly
2. **Educational** - Maintain instructional value
3. **Clear** - Use accessible language
4. **Example-supported** - Include relevant examples when they aid understanding
Provide only the direct answer to what was asked.
"""

def __init__(self, api_key: str, base_url: str, model: str):
self.client = openai.OpenAI(api_key=api_key, base_url=base_url)
self.model = model

# Pre-build base API parameters
self.base_params = {
"model": self.model,
"temperature": 0,
"max_tokens": 800
}

# Initialize tokenizer for usage tracking
try:
self.encoding = tiktoken.encoding_for_model("gpt-4") # Use gpt-4 encoding as fallback
except KeyError:
self.encoding = tiktoken.get_encoding("cl100k_base") # Default encoding

def generate_response(self, query: str,
conversation_history: Optional[str] = None,
tools: Optional[List] = None,
tool_manager=None) -> str:
"""
Generate AI response with optional tool usage and conversation context.

Args:
query: The user's question or request
conversation_history: Previous messages for context
tools: Available tools the AI can use
tool_manager: Manager to execute tools

Returns:
Generated response as string
"""

# Build system content efficiently - avoid string ops when possible
system_content = (
f"{self.SYSTEM_PROMPT}\n\nPrevious conversation:\n{conversation_history}"
if conversation_history
else self.SYSTEM_PROMPT
)

# Prepare messages for OpenAI format
messages = [
{"role": "system", "content": system_content},
{"role": "user", "content": query}
]

# Prepare API call parameters efficiently
api_params = {
**self.base_params,
"messages": messages
}

# Convert Anthropic tools to OpenAI functions format
if tools:
api_params["functions"] = self._convert_tools_to_functions(tools)
api_params["function_call"] = "auto"

# Get response from company LLM
response = self.client.chat.completions.create(**api_params)

# Handle function execution if needed
if response.choices[0].finish_reason == "function_call" and tool_manager:
return self._handle_function_execution(response, api_params, tool_manager)

# Return direct response
return response.choices[0].message.content

def _convert_tools_to_functions(self, tools: List) -> List[Dict]:
"""Convert Anthropic tool format to OpenAI function format"""
functions = []
for tool in tools:
function = {
"name": tool["name"],
"description": tool["description"],
"parameters": tool["input_schema"]
}
functions.append(function)
return functions

def _handle_function_execution(self, initial_response, base_params: Dict[str, Any], tool_manager):
"""
Handle execution of function calls and get follow-up response.

Args:
initial_response: The response containing function call requests
base_params: Base API parameters
tool_manager: Manager to execute tools

Returns:
Final response text after function execution
"""
# Start with existing messages
messages = base_params["messages"].copy()

# Get the function call from response
function_call = initial_response.choices[0].message.function_call

# Add AI's function call response
messages.append({
"role": "assistant",
"content": None,
"function_call": {
"name": function_call.name,
"arguments": function_call.arguments
}
})

# Execute function call
import json
function_args = json.loads(function_call.arguments)
function_result = tool_manager.execute_tool(function_call.name, **function_args)

# Add function result
messages.append({
"role": "function",
"name": function_call.name,
"content": str(function_result)
})

# Prepare final API call without functions
final_params = {
**self.base_params,
"messages": messages
}

# Get final response
final_response = self.client.chat.completions.create(**final_params)
return final_response.choices[0].message.content


class AIGenerator:
"""Handles interactions with Anthropic's Claude API for generating responses"""

Expand Down
7 changes: 6 additions & 1 deletion backend/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@
@dataclass
class Config:
"""Configuration settings for the RAG system"""
# Anthropic API settings
# Company LLM API settings (following LLMProvider.md specifications)
RDSEC_API_ENDPOINT: str = os.getenv("RDSEC_API_ENDPOINT", "")
RDSEC_API_KEY: str = os.getenv("RDSEC_API_KEY", "")
RDSEC_MODEL: str = "gpt-4o" # Using gpt-4o as specified in LLMProvider.md

# Legacy Anthropic settings (kept for fallback if needed)
ANTHROPIC_API_KEY: str = os.getenv("ANTHROPIC_API_KEY", "")
ANTHROPIC_MODEL: str = "claude-sonnet-4-20250514"

Expand Down
18 changes: 16 additions & 2 deletions backend/rag_system.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import os
from document_processor import DocumentProcessor
from vector_store import VectorStore
from ai_generator import AIGenerator
from ai_generator import AIGenerator, OpenAIGenerator
from session_manager import SessionManager
from search_tools import ToolManager, CourseSearchTool
from models import Course, Lesson, CourseChunk
Expand All @@ -16,7 +16,21 @@ def __init__(self, config):
# Initialize core components
self.document_processor = DocumentProcessor(config.CHUNK_SIZE, config.CHUNK_OVERLAP)
self.vector_store = VectorStore(config.CHROMA_PATH, config.EMBEDDING_MODEL, config.MAX_RESULTS)
self.ai_generator = AIGenerator(config.ANTHROPIC_API_KEY, config.ANTHROPIC_MODEL)

# Initialize AI generator with company LLM as primary
if config.RDSEC_API_KEY and config.RDSEC_API_ENDPOINT:
print("Using company RDSec LLM provider")
self.ai_generator = OpenAIGenerator(
config.RDSEC_API_KEY,
config.RDSEC_API_ENDPOINT,
config.RDSEC_MODEL
)
elif config.ANTHROPIC_API_KEY:
print("Falling back to Anthropic LLM provider")
self.ai_generator = AIGenerator(config.ANTHROPIC_API_KEY, config.ANTHROPIC_MODEL)
else:
raise ValueError("No valid LLM API key found. Please set RDSEC_API_KEY or ANTHROPIC_API_KEY in your .env file")

self.session_manager = SessionManager(config.MAX_HISTORY)

# Initialize search tools
Expand Down
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ requires-python = ">=3.13"
dependencies = [
"chromadb==1.0.15",
"anthropic==0.58.2",
"openai>=1.0.0",
"tiktoken>=0.5.0",
"sentence-transformers==5.0.0",
"fastapi==0.116.1",
"uvicorn==0.35.0",
Expand Down
3 changes: 2 additions & 1 deletion run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ if [ ! -d "backend" ]; then
fi

echo "Starting Course Materials RAG System..."
echo "Make sure you have set your ANTHROPIC_API_KEY in .env"
echo "Make sure you have set your RDSEC_API_KEY and RDSEC_API_ENDPOINT in .env"
echo "(Anthropic API key can be used as fallback)"

# Change to backend directory and start the server
cd backend && uv run uvicorn app:app --reload --port 8000
41 changes: 41 additions & 0 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.