feat: parse and include referenced issues as prompt context (#12)

This commit is contained in:
2025-02-23 13:08:08 +01:00
parent 3a18c25b3c
commit a49254c9ed
11 changed files with 229 additions and 23 deletions

View File

@ -1,6 +1,7 @@
package model
type Issue struct {
ID string
Content string
ID string
Title string
Body string
}

View File

@ -1,6 +1,7 @@
package model
type Project struct {
ID string
Label string
ID string
Name string
Description string
}

View File

@ -0,0 +1,8 @@
package model
type Resource struct {
Name string
Type string
Syntax string
Content string
}

View File

@ -12,8 +12,10 @@ var (
)
type Forge interface {
GetProjects(ctx context.Context) ([]*model.Project, error)
GetAllProjects(ctx context.Context) ([]*model.Project, error)
CreateIssue(ctx context.Context, projectID string, title string, body string) (string, error)
GetIssues(ctx context.Context, projectID string, issueIDs ...string) ([]*model.Issue, error)
GetIssueTemplate(ctx context.Context, projectID string) (string, error)
GetProjectLanguages(ctx context.Context, projectID string) ([]string, error)
GetProject(ctx context.Context, projectID string) (*model.Project, error)
}

View File

@ -4,6 +4,7 @@ import (
"context"
"fmt"
"log/slog"
"regexp"
"strings"
"time"
@ -38,6 +39,7 @@ type IssueManager struct {
forgeFactories []ForgeFactory
llmClient llm.Client
projectCache *cache.Cache[[]*model.Project]
forgeCache *cache.Cache[port.Forge]
}
func (m *IssueManager) CreateIssue(ctx context.Context, user *model.User, projectID string, title string, body string) (string, error) {
@ -64,7 +66,7 @@ func (m *IssueManager) GetUserProjects(ctx context.Context, user *model.User) ([
return nil, errors.WithStack(err)
}
refreshedProjects, err := forge.GetProjects(ctx)
refreshedProjects, err := forge.GetAllProjects(ctx)
if err != nil {
return nil, errors.WithStack(err)
}
@ -83,11 +85,15 @@ func (m *IssueManager) GenerateIssue(ctx context.Context, user *model.User, proj
return "", "", errors.WithStack(err)
}
slog.DebugContext(ctx, "using system prompt", slog.String("systemPrompt", systemPrompt))
userPrompt, err := m.getIssueUserPrompt(ctx, user, projectID, issueSummary)
if err != nil {
return "", "", errors.WithStack(err)
}
slog.DebugContext(ctx, "using user prompt", slog.String("userPrompt", userPrompt))
messages := []llm.Message{
llm.NewMessage(llm.RoleSystem, systemPrompt),
llm.NewMessage(llm.RoleUser, userPrompt),
@ -141,15 +147,36 @@ func (m *IssueManager) getIssueSystemPrompt(ctx context.Context, user *model.Use
}
func (m *IssueManager) getIssueUserPrompt(ctx context.Context, user *model.User, projectID string, issueSummary string) (string, error) {
_, err := m.getUserForge(ctx, user)
forge, err := m.getUserForge(ctx, user)
if err != nil {
return "", errors.WithStack(err)
}
project, err := forge.GetProject(ctx, projectID)
if err != nil {
return "", errors.WithStack(err)
}
projectLanguages, err := forge.GetProjectLanguages(ctx, projectID)
if err != nil {
return "", errors.WithStack(err)
}
resources, err := m.extractResources(ctx, forge, projectID, issueSummary)
if err != nil {
return "", errors.WithStack(err)
}
userPrompt, err := llm.PromptTemplate(issueUserPromptRawTemplate, struct {
Context string
Summary string
Project *model.Project
ProjectLanguages []string
Resources []*model.Resource
}{
Context: issueSummary,
Summary: issueSummary,
Project: project,
ProjectLanguages: projectLanguages,
Resources: resources,
})
if err != nil {
return "", errors.WithStack(err)
@ -159,6 +186,11 @@ func (m *IssueManager) getIssueUserPrompt(ctx context.Context, user *model.User,
}
func (m *IssueManager) getUserForge(ctx context.Context, user *model.User) (port.Forge, error) {
forge, exists := m.forgeCache.Get(user.AccessToken)
if exists {
return forge, nil
}
for _, f := range m.forgeFactories {
if !f.Match(user) {
continue
@ -170,17 +202,66 @@ func (m *IssueManager) getUserForge(ctx context.Context, user *model.User) (port
return nil, errors.WithStack(ErrForgeNotAvailable)
}
m.forgeCache.Set(user.AccessToken, forge, cache.DefaultExpiration)
return forge, nil
}
return nil, errors.New("no forge matching user found")
}
func (m *IssueManager) extractResources(ctx context.Context, forge port.Forge, projectID string, issueSummary string) ([]*model.Resource, error) {
resources := make([]*model.Resource, 0)
issues, err := m.extractIssues(ctx, forge, projectID, issueSummary)
if err != nil {
return nil, errors.Wrap(err, "could not extract issues")
}
resources = append(resources, issues...)
return resources, nil
}
var issueRefRegExp = regexp.MustCompile(`#([0-9]+)`)
func (m *IssueManager) extractIssues(ctx context.Context, forge port.Forge, projectID string, issueSummary string) ([]*model.Resource, error) {
issueRefMatches := issueRefRegExp.FindAllStringSubmatch(issueSummary, -1)
issueIDs := make([]string, 0, len(issueRefMatches))
for _, m := range issueRefMatches {
issueIDs = append(issueIDs, m[1])
}
issues, err := forge.GetIssues(ctx, projectID, issueIDs...)
if err != nil {
return nil, errors.WithStack(err)
}
resources := make([]*model.Resource, 0, len(issues))
for _, iss := range issues {
if iss == nil {
continue
}
resources = append(resources, &model.Resource{
Name: fmt.Sprintf("#%s - %s", iss.ID, iss.Title),
Type: "Issue",
Syntax: "",
Content: iss.Body,
})
}
return resources, nil
}
func NewIssueManager(llmClient llm.Client, forgeFactories ...ForgeFactory) *IssueManager {
return &IssueManager{
llmClient: llmClient,
forgeFactories: forgeFactories,
projectCache: cache.New[[]*model.Project](time.Minute*5, (time.Minute*5)/2),
forgeCache: cache.New[port.Forge](time.Minute, time.Minute/2),
}
}

View File

@ -9,11 +9,13 @@ You are an expert software developer with extensive experience in writing clear
- Expected behavior.
- Actual behavior.
- Any relevant error messages or logs.
- Always use the user prompt context main language.
- Always use the user prompt summary main language.
2. **Additional Context**:
- Include any other relevant information that might help in understanding or resolving the issue/request.
3. Let think step by step.
**Markdown Layout:**
```markdown

View File

@ -1,3 +1,32 @@
Write a formatted issue/request based on theses contextual informations:
Write a formatted issue/request based on the following informations:
{{ .Context }}
## Request Summary
{{ .Summary }}
## General Project Informations
- Name: {{ .Project.Name }}
- Description: {{ .Project.Description }}
- Languages:
{{- range .ProjectLanguages }}
- {{ . -}}
{{ end }}
{{ if gt (len .Resources) 0 }}
## Additional Resources
{{ range .Resources }}
### {{ .Name }}
**Type:**
{{ .Type }}
**Content:**
```{{ .Syntax }}
{{ .Content }}
```
{{end}}
{{end}}