✨Feat: 构建对话终端
This commit is contained in:
@@ -0,0 +1,155 @@
|
||||
package ai
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// SiliconFlowClient 硅基流动API客户端
|
||||
type SiliconFlowClient struct {
|
||||
apiKey string
|
||||
baseURL string
|
||||
model string
|
||||
timeout time.Duration
|
||||
maxRetries int
|
||||
}
|
||||
|
||||
// Config AI配置
|
||||
type Config struct {
|
||||
Endpoint string `yaml:"endpoint"`
|
||||
Model string `yaml:"model"`
|
||||
Timeout int `yaml:"timeout"`
|
||||
MaxRetries int `yaml:"max_retries"`
|
||||
ApiKey string `yaml:"api_key"`
|
||||
}
|
||||
|
||||
// ChatMessage 聊天消息
|
||||
type ChatMessage struct {
|
||||
Role string `json:"role"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
// ChatRequest 聊天请求
|
||||
type ChatRequest struct {
|
||||
Model string `json:"model"`
|
||||
Messages []ChatMessage `json:"messages"`
|
||||
Temperature float64 `json:"temperature"`
|
||||
Stream bool `json:"stream"`
|
||||
}
|
||||
|
||||
// ChatResponse 聊天响应
|
||||
type ChatResponse struct {
|
||||
ID string `json:"id"`
|
||||
Object string `json:"object"`
|
||||
Created int64 `json:"created"`
|
||||
Model string `json:"model"`
|
||||
Choices []Choice `json:"choices"`
|
||||
Usage Usage `json:"usage"`
|
||||
}
|
||||
|
||||
// Choice 选择项
|
||||
type Choice struct {
|
||||
Index int `json:"index"`
|
||||
Message ChatMessage `json:"message"`
|
||||
FinishReason string `json:"finish_reason"`
|
||||
}
|
||||
|
||||
// Usage 使用情况
|
||||
type Usage struct {
|
||||
PromptTokens int `json:"prompt_tokens"`
|
||||
CompletionTokens int `json:"completion_tokens"`
|
||||
TotalTokens int `json:"total_tokens"`
|
||||
}
|
||||
|
||||
// NewClient 创建新的AI客户端
|
||||
func NewClient(config Config) *SiliconFlowClient {
|
||||
return &SiliconFlowClient{
|
||||
apiKey: config.ApiKey,
|
||||
baseURL: config.Endpoint,
|
||||
model: config.Model,
|
||||
timeout: time.Duration(config.Timeout) * time.Second,
|
||||
maxRetries: config.MaxRetries,
|
||||
}
|
||||
}
|
||||
|
||||
// Generate 生成内容
|
||||
func (c *SiliconFlowClient) Generate(prompt string) (string, error) {
|
||||
reqBody := ChatRequest{
|
||||
Model: c.model,
|
||||
Messages: []ChatMessage{
|
||||
{
|
||||
Role: "user",
|
||||
Content: prompt,
|
||||
},
|
||||
},
|
||||
Temperature: 0.7,
|
||||
Stream: false,
|
||||
}
|
||||
|
||||
reqJSON, err := json.Marshal(reqBody)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to marshal request: %w", err)
|
||||
}
|
||||
|
||||
var lastErr error
|
||||
for attempt := 0; attempt < c.maxRetries; attempt++ {
|
||||
result, err := c.sendRequest(reqJSON)
|
||||
if err == nil {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
lastErr = err
|
||||
|
||||
if attempt < c.maxRetries-1 {
|
||||
time.Sleep(time.Duration(attempt+1) * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("failed after %d attempts: %w", c.maxRetries, lastErr)
|
||||
}
|
||||
|
||||
func (c *SiliconFlowClient) sendRequest(reqJSON []byte) (string, error) {
|
||||
url := c.baseURL + "/chat/completions"
|
||||
|
||||
req, err := http.NewRequest("POST", url, bytes.NewReader(reqJSON))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
|
||||
req.Header.Set("Authorization", "Bearer "+c.apiKey)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
client := &http.Client{
|
||||
Timeout: c.timeout,
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to send request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to read response: %w", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "", fmt.Errorf("API returned status code %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
var chatResp ChatResponse
|
||||
if err := json.Unmarshal(body, &chatResp); err != nil {
|
||||
return "", fmt.Errorf("failed to unmarshal response: %w", err)
|
||||
}
|
||||
|
||||
if len(chatResp.Choices) == 0 {
|
||||
return "", fmt.Errorf("no choices in response")
|
||||
}
|
||||
|
||||
return chatResp.Choices[0].Message.Content, nil
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package generators
|
||||
|
||||
import (
|
||||
"learning-assistant/internal/ai"
|
||||
)
|
||||
|
||||
// GeneratorType 生成器类型
|
||||
type GeneratorType string
|
||||
|
||||
const (
|
||||
GeneratorMermaid GeneratorType = "mermaid"
|
||||
GeneratorGuodegang GeneratorType = "guodegang"
|
||||
GeneratorQuiz GeneratorType = "quiz"
|
||||
GeneratorCard GeneratorType = "card"
|
||||
GeneratorScenario GeneratorType = "scenario"
|
||||
)
|
||||
|
||||
// GenerateResult 生成结果
|
||||
type GenerateResult struct {
|
||||
Type GeneratorType
|
||||
Content string
|
||||
Error error
|
||||
}
|
||||
|
||||
// BaseGenerator 基础生成器
|
||||
type BaseGenerator struct {
|
||||
client *ai.SiliconFlowClient
|
||||
promptManager *ai.PromptManager
|
||||
}
|
||||
|
||||
// NewBaseGenerator 创建基础生成器
|
||||
func NewBaseGenerator(client *ai.SiliconFlowClient, promptManager *ai.PromptManager) *BaseGenerator {
|
||||
return &BaseGenerator{
|
||||
client: client,
|
||||
promptManager: promptManager,
|
||||
}
|
||||
}
|
||||
|
||||
// Generate 生成内容
|
||||
func (g *BaseGenerator) Generate(genType GeneratorType, knowledge string, resultChan chan<- GenerateResult) {
|
||||
prompt, err := g.promptManager.GeneratePrompt(string(genType), knowledge)
|
||||
if err != nil {
|
||||
resultChan <- GenerateResult{
|
||||
Type: genType,
|
||||
Error: err,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
content, err := g.client.Generate(prompt)
|
||||
if err != nil {
|
||||
resultChan <- GenerateResult{
|
||||
Type: genType,
|
||||
Error: err,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
resultChan <- GenerateResult{
|
||||
Type: genType,
|
||||
Content: content,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package ai
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
"strings"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
//go:embed prompts/*.txt
|
||||
var promptFS embed.FS
|
||||
|
||||
// PromptManager 提示词管理器
|
||||
type PromptManager struct {
|
||||
templates map[string]*template.Template
|
||||
}
|
||||
|
||||
// NewPromptManager 创建提示词管理器
|
||||
func NewPromptManager() *PromptManager {
|
||||
pm := &PromptManager{
|
||||
templates: make(map[string]*template.Template),
|
||||
}
|
||||
|
||||
pm.loadTemplates()
|
||||
return pm
|
||||
}
|
||||
|
||||
func (pm *PromptManager) loadTemplates() {
|
||||
templateFiles := map[string]string{
|
||||
"mermaid": "prompts/mermaid.txt",
|
||||
"guodegang": "prompts/guodegang.txt",
|
||||
"quiz": "prompts/quiz.txt",
|
||||
"card": "prompts/card.txt",
|
||||
"scenario": "prompts/scenario.txt",
|
||||
}
|
||||
|
||||
for name, path := range templateFiles {
|
||||
content, err := promptFS.ReadFile(path)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
tmpl, err := template.New(name).Parse(string(content))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
pm.templates[name] = tmpl
|
||||
}
|
||||
}
|
||||
|
||||
// GeneratePrompt 生成提示词
|
||||
func (pm *PromptManager) GeneratePrompt(templateType string, knowledge string) (string, error) {
|
||||
tmpl, ok := pm.templates[templateType]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("template type %s not found", templateType)
|
||||
}
|
||||
|
||||
var buf strings.Builder
|
||||
data := struct {
|
||||
Knowledge string
|
||||
}{
|
||||
Knowledge: knowledge,
|
||||
}
|
||||
|
||||
if err := tmpl.Execute(&buf, data); err != nil {
|
||||
return "", fmt.Errorf("failed to execute template: %w", err)
|
||||
}
|
||||
|
||||
return buf.String(), nil
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
为"{{.Knowledge}}"制作知识记忆卡片,帮助用户快速掌握关键知识点,要求:
|
||||
1. 结构清晰,重点突出
|
||||
2. 使用emoji图标增强可读性
|
||||
3. 每个部分简洁明了,方便记忆
|
||||
|
||||
输出格式:
|
||||
📌 核心概念:[精确的知识点定义,50字以内]
|
||||
|
||||
🔑 关键要点:
|
||||
- 要点1(具体说明,20字以内)
|
||||
- 要点2(具体说明,20字以内)
|
||||
- 要点3(具体说明,20字以内)
|
||||
- 要点4(具体说明,20字以内)
|
||||
|
||||
⚠️ 常见误区:
|
||||
- 误区1:[说明]
|
||||
- 误区2:[说明]
|
||||
|
||||
✅ 最佳实践:
|
||||
- 实践1:[具体建议]
|
||||
- 实践2:[具体建议]
|
||||
|
||||
🎯 记忆口诀:[朗朗上口的口诀,帮助记忆]
|
||||
@@ -0,0 +1,12 @@
|
||||
用北京话、郭德纲相声演员的口吻讲解"{{.Knowledge}}",要求:
|
||||
1. 开头用"咳咳,咱今儿个来说说这个{{.Knowledge}}..."作为开场
|
||||
2. 中间穿插一些相声评书风格的生动比喻和例子
|
||||
3. 语气轻松诙谐、接地气,但内容要准确专业
|
||||
4. 偶尔使用相声常用语,比如"您听好了"、"这就好比"、"想当年"、"您猜怎么着"
|
||||
5. 每讲完一个要点,可以用"这话怎么说呢?"来过渡
|
||||
6. 最后用"您记住了吗?下次要是有人问起来,您就把这么一通摆出来,准保让他对您刮目相看!"收尾
|
||||
|
||||
注意:
|
||||
- 不要过度夸张,保持内容的准确性和专业性
|
||||
- 语言要通俗易懂,避免过于晦涩的专业术语
|
||||
- 字数控制在300-500字
|
||||
@@ -0,0 +1,19 @@
|
||||
请为"{{.Knowledge}}"生成一个Mermaid思维导图或流程图,要求:
|
||||
1. 使用graph TD或flowchart TD格式
|
||||
2. 清晰展示知识点的结构层次和关系
|
||||
3. 关键节点使用中文标签
|
||||
4. 适当使用不同的节点样式区分概念类型
|
||||
5. 保持代码简洁,便于复制使用
|
||||
|
||||
输出格式(只输出代码,不要其他说明文字):
|
||||
```mermaid
|
||||
graph TD
|
||||
A[{{.Knowledge}}] --> B(核心概念1)
|
||||
A --> C(核心概念2)
|
||||
B --> B1(子概念1.1)
|
||||
B --> B2(子概念1.2)
|
||||
C --> C1(子概念2.1)
|
||||
style A fill:#f9f,stroke:#333,stroke-width:4px
|
||||
style B fill:#bbf,stroke:#333,stroke-width:2px
|
||||
style C fill:#bbf,stroke:#333,stroke-width:2px
|
||||
```
|
||||
@@ -0,0 +1,22 @@
|
||||
关于"{{.Knowledge}}",生成10道单选题,要求:
|
||||
1. 题目类型包括:概念理解、原理分析、应用场景、最佳实践等
|
||||
2. 难度分布:3道基础题、4道中级题、3道进阶题
|
||||
3. 每道题都要提供详细的解析说明
|
||||
4. 选项之间要有明显的区别,避免模棱两可
|
||||
|
||||
必须严格按照以下JSON格式输出(只输出JSON,不要其他说明文字):
|
||||
{
|
||||
"questions": [
|
||||
{
|
||||
"question": "题目文字,准确描述问题",
|
||||
"options": [
|
||||
"A. 选项内容",
|
||||
"B. 选项内容",
|
||||
"C. 选项内容",
|
||||
"D. 选项内容"
|
||||
],
|
||||
"answer": "A",
|
||||
"explanation": "详细解析为什么选这个答案,以及为什么其他选项不对"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
为"{{.Knowledge}}"提供3个实际的工程应用场景,要求:
|
||||
1. 每个场景都要具体、真实、有代表性
|
||||
2. 包含完整的问题背景、解决方案、代码示例
|
||||
3. 代码使用伪代码或简化的实现,注重逻辑清晰
|
||||
4. 说明使用该知识点解决了什么核心问题
|
||||
5. 强调注意事项和最佳实践
|
||||
|
||||
输出格式:
|
||||
|
||||
场景1:[场景名称,如"高并发缓存更新"]
|
||||
背景:
|
||||
[描述实际业务场景和遇到的问题]
|
||||
|
||||
方案:
|
||||
[说明如何使用{{.Knowledge}}解决问题,分步骤说明]
|
||||
|
||||
代码示例:
|
||||
[使用伪代码或简化代码展示实现方式]
|
||||
|
||||
注意事项:
|
||||
- 关键点1
|
||||
- 关键点2
|
||||
|
||||
场景2:[场景名称]
|
||||
背景:
|
||||
...
|
||||
|
||||
方案:
|
||||
...
|
||||
|
||||
代码示例:
|
||||
...
|
||||
|
||||
注意事项:
|
||||
- ...
|
||||
|
||||
场景3:[场景名称]
|
||||
背景:
|
||||
...
|
||||
|
||||
方案:
|
||||
...
|
||||
|
||||
代码示例:
|
||||
...
|
||||
|
||||
注意事项:
|
||||
- ...
|
||||
Reference in New Issue
Block a user