feat: parse and include referenced issues as prompt context (#12)
This commit is contained in:
@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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}}
|
Reference in New Issue
Block a user