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
404 changes: 404 additions & 0 deletions docs/CODE_CACHE.md

Large diffs are not rendered by default.

254 changes: 233 additions & 21 deletions lib/coderacer/ai.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,51 @@ defmodule Coderacer.AI do
@moduledoc """
Module documentation for Coderacer.AI.
"""

def generate(language, difficulty, lines \\ 10) do
# First try to get from cache
case Coderacer.CodeCache.get_code(language, difficulty, lines) do
{:ok, cached_code} ->
{:ok, cached_code}

{:error, :not_found} ->
# Fallback to live generation if not in cache
generate_live(language, difficulty, lines)
end
end

def generate_live(language, difficulty, lines \\ 10) do
# Simulate code generation based on language and difficulty
system =
"""
You are a code generation assistant that creates diverse, real-world programming exercises.

DIFFICULTY LEVELS:
- Easy: Simple syntax, common patterns, basic control structures, short variable names
- Medium: Moderate complexity, some nesting, standard library usage, descriptive names
- Hard: Complex syntax, advanced patterns, multiple concepts combined, longer identifiers

REQUIREMENTS:
1. Generate exactly #{lines} lines of functional, compilable code
2. Use real-world scenarios (web apps, data processing, algorithms, etc.)
3. Follow language best practices and conventions
4. Vary code patterns - avoid repetitive structures
5. Include diverse concepts: functions, classes, loops, conditionals, data structures
6. Use realistic variable/function names, not placeholders

OUTPUT FORMAT:
Return only the raw code without markdown, comments explaining the exercise, or extra text.
The code should be immediately usable and represent a complete, meaningful snippet.
"""

prompt = """
Generate exactly #{lines} lines of #{language} code with #{difficulty} typing difficulty.
Generate at least #{lines} lines of #{language} code with #{difficulty} typing difficulty.

Context: Create a practical code snippet that demonstrates real-world usage.
Ensure variety in syntax patterns and avoid repetitive structures.
"""

case send(prompt) do
case send_to_gemini(system, prompt) do
%Req.Response{status: 200, body: body} ->
result =
parse_body(body)
Expand All @@ -24,38 +59,135 @@ defmodule Coderacer.AI do
end
end

def send(prompt, lines \\ 10) do
url =
"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-lite:generateContent?key=#{System.get_env("GEMINI_API_KEY")}"
def analyze(session) do
system =
"""
You are a specialized AI assistant that evaluates developer typing proficiency for programming languages.
"""

prompt =
"""
Analyze typing test results and determine programming language suitability based on typing performance.
Input Data:

Typing test results:
Difficulty: #{session.difficulty}
Code Length: #{String.length(session.code_challenge)} chars
#{round(String.length(session.code_challenge) / session.time_completion * 60)} Characters/Min
#{round(session.streak / (session.streak + session.wrong) * 100)}% Accuracy
#{session.time_completion}s Time Taken
#{session.wrong} Wrong

Target programming language: #{session.language}


Context: Typing proficiency directly impacts developer productivity, coding speed, and idea implementation. Different programming languages have varying typing demands - some require extensive special character usage, others have verbose syntax, while some leverage code completion tools more heavily.

Analysis Framework:

Evaluate Core Metrics: Examine characters per minute (CPM), accuracy, and other provided metrics
Language-Specific Assessment: Consider the chosen language's typing characteristics:

Special character frequency (brackets, operators, symbols)
Syntax verbosity vs. conciseness
Common development patterns and code completion reliance

Impact Assessment: Determine how typing skills affect efficiency in the specific language

Output Requirements:
Analysis:

[Bullet point analysis of typing strengths and weaknesses]
[Language-specific typing requirements evaluation]
[Performance impact assessment for chosen programming language]

Call to Action:
[Provide encouraging feedback with specific improvement recommendations]
Verdict:
[Select one: "Highly Suitable" | "Suitable" | "Marginally Suitable" | "Not Suitable"]
[Include brief justification]
Important: Base your assessment exclusively on the typing test data and programming language characteristics. Do not infer other programming skills or experience levels.
"""

case send_to_gemini(system, prompt, "generateContent") do
%Req.Response{status: 200, body: body} ->
result =
parse_body(body)
|> parse_json()

result

%Req.Response{status: status, body: body} ->
{:error, status, parse_error(body)}
end
end

def analyze_stream(session, callback_fn) do
require Logger
Logger.info("Starting AI analysis stream for session #{session.id}")

system =
"""
You are a code generation assistant that creates diverse, real-world programming exercises.
You are a specialized AI assistant that evaluates developer typing proficiency for programming languages.
"""

DIFFICULTY LEVELS:
- Easy: Simple syntax, common patterns, basic control structures, short variable names
- Medium: Moderate complexity, some nesting, standard library usage, descriptive names
- Hard: Complex syntax, advanced patterns, multiple concepts combined, longer identifiers
prompt =
"""
Analyze typing test results and determine programming language suitability based on typing performance.
Input Data:

REQUIREMENTS:
1. Generate exactly #{lines} lines of functional, compilable code
2. Use real-world scenarios (web apps, data processing, algorithms, etc.)
3. Follow language best practices and conventions
4. Vary code patterns - avoid repetitive structures
5. Include diverse concepts: functions, classes, loops, conditionals, data structures
6. Use realistic variable/function names, not placeholders
Typing test results:
Difficulty: #{session.difficulty}
Code Length: #{String.length(session.code_challenge)} chars
#{round(String.length(session.code_challenge) / session.time_completion * 60)} Characters/Min
#{round(session.streak / (session.streak + session.wrong) * 100)}% Accuracy
#{session.time_completion}s Time Taken
#{session.wrong} Wrong

OUTPUT FORMAT:
Return only the raw code without markdown, comments explaining the exercise, or extra text.
The code should be immediately usable and represent a complete, meaningful snippet.
Target programming language: #{session.language}


Context: Typing proficiency directly impacts developer productivity, coding speed, and idea implementation. Different programming languages have varying typing demands - some require extensive special character usage, others have verbose syntax, while some leverage code completion tools more heavily.

Analysis Framework:

Evaluate Core Metrics: Examine characters per minute (CPM), accuracy, and other provided metrics
Language-Specific Assessment: Consider the chosen language's typing characteristics:

Special character frequency (brackets, operators, symbols)
Syntax verbosity vs. conciseness
Common development patterns and code completion reliance

Impact Assessment: Determine how typing skills affect efficiency in the specific language

Output Requirements:
Analysis:

[Bullet point analysis of typing strengths and weaknesses]
[Language-specific typing requirements evaluation]
[Performance impact assessment for chosen programming language]

Call to Action:
[Provide encouraging feedback with specific improvement recommendations]
Verdict:
[Select one: "Highly Suitable" | "Suitable" | "Marginally Suitable" | "Not Suitable"]
[Include brief justification]
Important: Base your assessment exclusively on the typing test data and programming language characteristics. Do not infer other programming skills or experience levels.
"""

send_to_gemini_stream(system, prompt, callback_fn)
end

def send_to_gemini(system, prompt, mode \\ "generateContent") do
url =
"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-preview-05-20:#{mode}?key=#{System.get_env("GEMINI_API_KEY")}"

http_client = Application.get_env(:coderacer, :http_client, Req)

http_client.post!(url,
json: %{
contents: [
%{role: "assistant", parts: [%{text: system}]},
%{role: "model", parts: [%{text: system}]},
%{role: "user", parts: [%{text: prompt}]}
],
generationConfig: %{
Expand All @@ -78,6 +210,86 @@ defmodule Coderacer.AI do
)
end

def send_to_gemini_stream(system, prompt, callback_fn) do
require Logger
Logger.info("Sending streaming request to Gemini API")

url =
"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-preview-05-20:streamGenerateContent?key=#{System.get_env("GEMINI_API_KEY")}"

http_client = Application.get_env(:coderacer, :http_client, Req)

# Use streaming request
result =
http_client.post!(url,
json: %{
contents: [
%{role: "model", parts: [%{text: system}]},
%{role: "user", parts: [%{text: prompt}]}
],
generationConfig: %{
temperature: 0.7,
topP: 0.8,
max_output_tokens: 65_536
}
},
into: fn
{:status, status} when status == 200 ->
Logger.info("Streaming started successfully with status 200")
{:cont, status}

{:status, status} ->
Logger.error("Streaming failed with status #{status}")
{:halt, {:error, status}}

{:headers, _headers} ->
{:cont, nil}

{:data, chunk} ->
Logger.debug("Received chunk: #{inspect(String.slice(chunk, 0, 100))}...")
# Parse SSE chunks
chunk
|> String.split("\n")
|> Enum.filter(&String.starts_with?(&1, "data: "))
|> Enum.each(fn line ->
content = String.trim_leading(line, "data: ")

unless content == "[DONE]" or content == "" do
case Jason.decode(content) do
{:ok, json} ->
case extract_text_from_chunk(json) do
nil ->
Logger.debug("No text found in chunk")

text ->
Logger.debug(
"Extracted text chunk: #{inspect(String.slice(text, 0, 50))}..."
)

callback_fn.(text)
end

{:error, error} ->
Logger.warning("Failed to decode JSON chunk: #{inspect(error)}")
end
end
end)

{:cont, nil}
end
)

Logger.info("Streaming completed with result: #{inspect(result)}")
result
end

defp extract_text_from_chunk(json) do
case get_in(json, ["candidates", Access.at(0), "content", "parts", Access.at(0), "text"]) do
nil -> nil
text when is_binary(text) -> text
end
end

def parse_body(body) do
body
|> Map.get("candidates")
Expand Down
2 changes: 2 additions & 0 deletions lib/coderacer/application.ex
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ defmodule Coderacer.Application do
repos: Application.fetch_env!(:coderacer, :ecto_repos), skip: skip_migrations?()},
{DNSCluster, query: Application.get_env(:coderacer, :dns_cluster_query) || :ignore},
{Phoenix.PubSub, name: Coderacer.PubSub},
# Code cache for periodic AI code generation
Coderacer.CodeCache,
# Start a worker by calling: Coderacer.Worker.start_link(arg)
# {Coderacer.Worker, arg},
# Start to serve requests, typically the last entry
Expand Down
Loading