LLM-Powered Documentation Generation
Automatically generate and maintain technical documentation using LLMs integrated into your development workflow.
Documentation is the first thing to go stale and the last thing developers want to update. LLMs can change that — not by replacing human-written docs, but by generating first drafts and flagging when existing docs drift from the code.
The Documentation Pipeline
Our approach: extract code context, generate docs, diff against existing docs, and create PRs for review.
defmodule Docs.Generator do
def generate_for_module(module_path) do
source = File.read!(module_path)
prompt = """
Generate documentation for this Elixir module.
Include:
- Module overview (2-3 sentences)
- Each public function: purpose, params, return value, example usage
- Any important notes about side effects or error handling
Format as Markdown. Be concise and accurate.
```elixir
#{source}
```
"""
{:ok, docs} = LLM.complete(prompt, model: "claude-sonnet-4-6")
{:ok, docs}
end
end
Drift Detection
The real value is catching when docs don’t match code:
defmodule Docs.DriftDetector do
def check(module_path, doc_path) do
source = File.read!(module_path)
existing_docs = File.read!(doc_path)
prompt = """
Compare this code against its documentation.
Identify any discrepancies:
- Functions documented but not in code
- Functions in code but not documented
- Parameter mismatches
- Incorrect descriptions
Code:
```elixir
#{source}
```
Documentation:
```markdown
#{existing_docs}
```
Respond with JSON:
{
"is_current": true/false,
"issues": [{"type": "missing|outdated|incorrect", "description": "..."}]
}
"""
{:ok, response} = LLM.complete(prompt)
Jason.decode!(response)
end
end
Integrating into CI
Run drift detection on every PR that touches source code:
defmodule Docs.CI do
def check_pr(changed_files) do
changed_files
|> Enum.filter(&source_file?/1)
|> Enum.map(fn file ->
doc_path = source_to_doc_path(file)
if File.exists?(doc_path) do
Docs.DriftDetector.check(file, doc_path)
else
%{is_current: false, issues: [%{type: "missing", description: "No docs for #{file}"}]}
end
end)
|> Enum.filter(&(not &1.is_current))
end
end
What Works Well
LLMs are excellent at generating API reference documentation, function-level docstrings, and changelog entries. They’re less reliable for architectural overviews and tutorials, which require understanding intent and context that goes beyond the code.
The sweet spot: let the LLM generate the first draft, then have a human review and refine. This cuts documentation time by roughly 60% while maintaining quality.
Changelog Generation
One particularly effective workflow — auto-generating changelogs from commit history:
defmodule Docs.Changelog do
def generate(from_tag, to_tag) do
{:ok, commits} = Git.log(from_tag, to_tag)
prompt = """
Generate a user-facing changelog from these git commits.
Group by: Added, Changed, Fixed, Removed.
Write for end users, not developers — focus on what changed, not how.
Skip internal refactoring and dependency updates.
Commits:
#{format_commits(commits)}
"""
{:ok, changelog} = LLM.complete(prompt)
{:ok, changelog}
end
end
This runs as part of our release process. The LLM draft goes into a PR, gets a quick human review, and ships with the release. No more “we’ll write the changelog later” that never happens.