internal/infra/inference/reporter.go
internal/infra/inference · 250 lines · 11 declarations · source
Declarations
var reportPromptFile
var reportPromptFile []byte
The report prompt is a file for the same reasons the extraction prompt is, and compiled in for the same reason: a report is what a thematic question is answered from, so a prompt the filesystem can change is a path by which whoever can write next to the binary changes what this system says about somebody's memory.
var reportPrompt
var reportPrompt = mustLoadReportPrompt(reportPromptFile)
reportPrompt is parsed once, at startup. A panic is right: the file is embedded, so a failure is a binary that must not start rather than a runtime condition that might clear.
func mustLoadReportPrompt
func mustLoadReportPrompt(raw []byte) reportPromptFileShape
type Reporter
type Reporter struct {
config Config
client *http.Client
}
Reporter writes community reports by asking a chat model.
It writes prose and nothing more. What material goes in, what is refused on the way out, and how a context is cut when it does not fit are all in the report package — those are the decisions with a correctness argument, and they must not vary with the vendor.
func NewReporter
func NewReporter(config Config) *Reporter
NewReporter builds a report writer against an OpenAI-compatible endpoint.
method Reporter.Write
func (r *Reporter) Write(ctx context.Context, c report.Context) (report.Report, error)
Write asks the model to describe one community.
type reportEnvelope
type reportEnvelope struct {
Title string `json:"title"`
Summary string `json:"summary"`
Importance float32 `json:"importance"`
ImportanceReason string `json:"importance_reason"`
Findings []struct {
Summary string `json:"summary"`
Explanation string `json:"explanation"`
} `json:"findings"`
}
reportEnvelope is the reply shape the prompt asks for.
func decodeReport
func decodeReport(content string) (report.Report, error)
decodeReport reads the reply, locating the JSON inside whatever the model actually sent.
The same problem extraction has and the same answer: a model in JSON mode still wraps its answer in prose or a code fence often enough that a plain Unmarshal turns a good report into a failure. The object is recognised by its title, so a fragment that happens to parse is skipped rather than returned as a report with everything empty.
func renderContext
func renderContext(c report.Context) string
renderContext writes the material out, fenced.
The order is fixed — entities, then relations, then sub-reports — because a model given the same material in a different arrangement writes a different paragraph, and two rebuilds of one community would then differ for no reason anybody could name.
func materialText
func materialText(c report.Context) string
materialText is every byte of somebody else's words in this context, which is what the fence has to be absent from. Assembled separately rather than checked afterwards, because a fence chosen against half the material is a fence the other half can contain.
func reportSystemPrompt
func reportSystemPrompt() string
reportSystemPrompt composes the prompt in an order the file does not choose.
data_boundary is last for the reason it is last in extraction: that section's job is to be the final word before somebody else's text arrives, and a file that could reorder the sections could put it in the middle, where an instruction after it appears to supersede it.