Generating llms.txt for a repository¶
This example builds an llms.txt summary of a code repository by composing four ChainOfThought predictors. It demonstrates signatures with List[String] fields and threading predictor outputs through an Either for-comprehension.
Analysis signatures¶
trait AnalyzeRepository extends Spec:
def repo_url: InputField[String]
def file_tree: InputField[String]
def readme_content: InputField[String]
def project_purpose: OutputField[String]
def key_concepts: OutputField[List[String]]
def architecture_overview: OutputField[String]
trait AnalyzeCodeStructure extends Spec:
def file_tree: InputField[String]
def package_files: InputField[String]
def important_directories: OutputField[List[String]]
def entry_points: OutputField[List[String]]
def development_info: OutputField[String]
trait GenerateLLMsTxt extends Spec:
def project_purpose: InputField[String]
def key_concepts: InputField[List[String]]
def architecture_overview: InputField[String]
def important_directories: InputField[List[String]]
def entry_points: InputField[List[String]]
def development_info: InputField[String]
def usage_examples: InputField[String]
def llms_txt_content: OutputField[String]
Each Spec declares its inputs and outputs. List[String] outputs such as key_concepts and entry_points map to Scala List[String]. The fields of GenerateLLMsTxt line up with the combined outputs of the two analysis signatures plus a generated usage_examples string.
Composing the predictors¶
final class RepositoryAnalyzer:
private val analyzeRepo = ChainOfThought(Signature.of[AnalyzeRepository])
private val analyzeStructure = ChainOfThought(Signature.of[AnalyzeCodeStructure])
private val generateExamples = ChainOfThought(Signature.fromString("repo_info -> usage_examples"))
private val generateLlmsTxt = ChainOfThought(Signature.of[GenerateLLMsTxt])
def forward(
repoUrl : String,
fileTree : String,
readmeContent: String,
packageFiles : String
)(using RuntimeContext): Either[DspyError, String] =
for
repo <- analyzeRepo((repo_url = repoUrl, file_tree = fileTree, readme_content = readmeContent))
structure <- analyzeStructure((file_tree = fileTree, package_files = packageFiles))
examples <- generateExamples(
(repo_info = s"Purpose: ${repo.output.project_purpose}\nConcepts: ${repo.output.key_concepts}")
)
llms <- generateLlmsTxt((
project_purpose = repo.output.project_purpose,
key_concepts = repo.output.key_concepts,
architecture_overview = repo.output.architecture_overview,
important_directories = structure.output.important_directories,
entry_points = structure.output.entry_points,
development_info = structure.output.development_info,
usage_examples = examples.output.usage_examples
))
yield llms.output.llms_txt_content
RepositoryAnalyzer holds four ChainOfThought fields. Three are built with Signature.of, and generateExamples uses an inline string signature. forward runs them in sequence inside a for-comprehension over Either[DspyError, String], so a failure in any step short-circuits. Each predictor's output feeds the next, and the final step returns the llms_txt_content string.
Running it¶
OPENAI_API_KEY=sk-... sbt "examples/runMain dspy4s.examples.tutorials.llms_txt_generation.llmsTxtMain"
Notes¶
Fetching the file tree, README, and package files over the GitHub API is out of scope. That step is plain HTTP I/O, so supply fileTree, readmeContent, and packageFiles to forward however you like.
Full source: LlmsTxtGeneration.scala