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](instructions = "Analyze a repository structure and identify key components.")
)
private val analyzeStructure = ChainOfThought(
Signature.of[AnalyzeCodeStructure](
instructions = "Analyze code structure to identify important directories and files."
)
)
private val generateExamples = ChainOfThought(Signature.fromString("repo_info -> usage_examples"))
private val generateLlmsTxt = ChainOfThought(
Signature.of[GenerateLLMsTxt](
instructions = "Generate a comprehensive llms.txt file from analyzed repository information."
)
)
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))
concepts = repo.output.key_concepts.mkString(", ")
examples <- generateExamples((repo_info = s"Purpose: ${repo.output.project_purpose}\nConcepts: $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. The instructions argument gives the task text that the model gets. 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