title_generator.go 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. package llm
  2. import (
  3. "context"
  4. "fmt"
  5. "strings"
  6. "github.com/0xJacky/Nginx-UI/settings"
  7. "github.com/sashabaranov/go-openai"
  8. "github.com/uozi-tech/cosy/logger"
  9. )
  10. // GenerateSessionTitle generates a concise title for an LLM session based on the conversation context
  11. func GenerateSessionTitle(messages []openai.ChatCompletionMessage) (string, error) {
  12. client, err := GetClient()
  13. if err != nil {
  14. return "", fmt.Errorf("failed to get LLM client: %w", err)
  15. }
  16. // Create a summarized context from the first few messages
  17. messageContext := extractContextForTitleGeneration(messages)
  18. if messageContext == "" {
  19. return "New Session", nil
  20. }
  21. // Prepare the system message for title generation
  22. systemMessage := openai.ChatCompletionMessage{
  23. Role: openai.ChatMessageRoleSystem,
  24. Content: `You are a helpful assistant that generates concise, descriptive titles for chat sessions.
  25. Based on the conversation context provided, generate a short title (2-6 words) that captures the main topic or purpose.
  26. The title should be clear, specific, and professional.
  27. Respond only with the title, no additional text or formatting.`,
  28. }
  29. userMessage := openai.ChatCompletionMessage{
  30. Role: openai.ChatMessageRoleUser,
  31. Content: fmt.Sprintf("Generate a title for this conversation:\n\n%s", messageContext),
  32. }
  33. req := openai.ChatCompletionRequest{
  34. Model: settings.OpenAISettings.Model,
  35. Messages: []openai.ChatCompletionMessage{systemMessage, userMessage},
  36. MaxTokens: 20, // Keep it short
  37. Temperature: 0.3, // Lower temperature for more consistent titles
  38. }
  39. resp, err := client.CreateChatCompletion(context.Background(), req)
  40. if err != nil {
  41. logger.Error("Failed to generate session title:", err)
  42. return "", fmt.Errorf("failed to generate title: %w", err)
  43. }
  44. if len(resp.Choices) == 0 {
  45. return "New Session", nil
  46. }
  47. title := strings.TrimSpace(resp.Choices[0].Message.Content)
  48. // Sanitize the title
  49. title = sanitizeTitle(title)
  50. if title == "" {
  51. return "New Session", nil
  52. }
  53. return title, nil
  54. }
  55. // extractContextForTitleGeneration extracts relevant context from messages for title generation
  56. func extractContextForTitleGeneration(messages []openai.ChatCompletionMessage) string {
  57. if len(messages) == 0 {
  58. return ""
  59. }
  60. var contextBuilder strings.Builder
  61. messageCount := 0
  62. maxMessages := 3 // Only use the first few messages for context
  63. maxLength := 800 // Limit total context length
  64. for _, message := range messages {
  65. if messageCount >= maxMessages {
  66. break
  67. }
  68. // Skip system messages for title generation
  69. if message.Role == openai.ChatMessageRoleSystem {
  70. continue
  71. }
  72. content := strings.TrimSpace(message.Content)
  73. if content == "" {
  74. continue
  75. }
  76. // Add role prefix for clarity
  77. rolePrefix := ""
  78. switch message.Role {
  79. case openai.ChatMessageRoleUser:
  80. rolePrefix = "User: "
  81. case openai.ChatMessageRoleAssistant:
  82. rolePrefix = "Assistant: "
  83. }
  84. // Truncate very long messages
  85. if len(content) > 200 {
  86. content = content[:200] + "..."
  87. }
  88. newContent := fmt.Sprintf("%s%s\n", rolePrefix, content)
  89. // Check if adding this message would exceed the max length
  90. if contextBuilder.Len()+len(newContent) > maxLength {
  91. break
  92. }
  93. contextBuilder.WriteString(newContent)
  94. messageCount++
  95. }
  96. return contextBuilder.String()
  97. }
  98. // sanitizeTitle cleans up the generated title
  99. func sanitizeTitle(title string) string {
  100. // Remove quotes if present
  101. title = strings.Trim(title, `"'`)
  102. // Remove any prefix like "Title: " if present
  103. if strings.HasPrefix(strings.ToLower(title), "title:") {
  104. title = strings.TrimSpace(title[6:])
  105. }
  106. // Limit length
  107. if len(title) > 50 {
  108. title = title[:47] + "..."
  109. }
  110. // Replace any problematic characters
  111. title = strings.ReplaceAll(title, "\n", " ")
  112. title = strings.ReplaceAll(title, "\r", " ")
  113. // Collapse multiple spaces
  114. for strings.Contains(title, " ") {
  115. title = strings.ReplaceAll(title, " ", " ")
  116. }
  117. return strings.TrimSpace(title)
  118. }