feat(openai): 添加自定义 prompt 功能

- 更新前端设置组件,增加自定义 prompt 输入框
- 更新后端设置 API,支持保存和读取 prompt 配置
This commit is contained in:
spark 2025-02-01 11:32:38 +08:00
parent f6297d224c
commit 71289d1408
6 changed files with 32 additions and 10 deletions

View File

@ -21,6 +21,7 @@ type OpenAi struct {
Model string `json:"model"` Model string `json:"model"`
MaxTokens int `json:"max_tokens"` MaxTokens int `json:"max_tokens"`
Temperature float64 `json:"temperature"` Temperature float64 `json:"temperature"`
Prompt string `json:"prompt"`
} }
func NewDeepSeekOpenAi() *OpenAi { func NewDeepSeekOpenAi() *OpenAi {
@ -31,6 +32,7 @@ func NewDeepSeekOpenAi() *OpenAi {
Model: config.OpenAiModelName, Model: config.OpenAiModelName,
MaxTokens: config.OpenAiMaxTokens, MaxTokens: config.OpenAiMaxTokens,
Temperature: config.OpenAiTemperature, Temperature: config.OpenAiTemperature,
Prompt: config.Prompt,
} }
} }
@ -132,7 +134,7 @@ func (o OpenAi) NewChatStream(stock, stockCode string) <-chan string {
"role": "system", "role": "system",
//"content": "作为一位专业的A股市场分析师和投资顾问,请你根据以下信息提供详细的技术分析和投资策略建议:", //"content": "作为一位专业的A股市场分析师和投资顾问,请你根据以下信息提供详细的技术分析和投资策略建议:",
//"content": "【角色设定】\n你是一位拥有20年实战经验的顶级股票分析师精通技术分析、基本面分析、市场心理学和量化交易。擅长发现成长股、捕捉行业轮动机会在牛熊市中都能保持稳定收益。你的风格是价值投资与技术择时相结合注重风险控制。\n\n【核心功能】\n\n市场分析维度\n\n宏观经济GDP/CPI/货币政策)\n\n行业景气度产业链/政策红利/技术革新)\n\n个股三维诊断\n\n基本面PE/PB/ROE/现金流/护城河\n\n技术面K线形态/均线系统/量价关系/指标背离\n\n资金面主力动向/北向资金/融资余额/大宗交易\n\n智能策略库\n√ 趋势跟踪策略(鳄鱼线+ADX\n√ 波段交易策略(斐波那契回撤+RSI\n√ 事件驱动策略(财报/并购/政策)\n√ 量化对冲策略(α/β分离)\n\n风险管理体系\n▶ 动态止损ATR波动止损法\n▶ 仓位控制:凯利公式优化\n▶ 组合对冲:跨市场/跨品种对冲\n\n【工作流程】\n\n接收用户指令行业/市值/风险偏好)\n\n调用多因子选股模型初筛\n\n人工智慧叠加分析\n\n自然语言处理解读年报管理层讨论\n\n卷积神经网络识别K线形态\n\n知识图谱分析产业链关联\n\n生成投资建议附压力测试结果\n\n【输出要求】\n★ 结构化呈现:\n① 核心逻辑3点关键驱动力\n② 买卖区间(理想建仓/加仓/止盈价位)\n③ 风险警示(最大回撤概率)\n④ 替代方案(同类备选标的)\n\n【注意事项】\n※ 严格遵守监管要求,不做收益承诺\n※ 区分投资建议与市场观点\n※ 重要数据标注来源及更新时间\n※ 根据用户认知水平调整专业术语密度\n\n【教育指导】\n当用户提问时采用苏格拉底式追问\n\"您更关注短期事件驱动还是长期价值发现?\"\n\"当前仓位是否超过总资产的30%\"\n\"是否了解科创板与主板的交易规则差异?\"\n\n示例输出格式\n📈 标的名称XXXXXX\n⚖ 多空信号:金叉确认/顶背离预警\n🎯 关键价位支撑位XX.XX/压力位XX.XX\n📊 建议仓位核心仓位X%+卫星仓位X%\n⏳ 持有周期短线1-3周/中线(季度轮动)\n🔍 跟踪要素重点关注Q2毛利率变化及股东减持进展", //"content": "【角色设定】\n你是一位拥有20年实战经验的顶级股票分析师精通技术分析、基本面分析、市场心理学和量化交易。擅长发现成长股、捕捉行业轮动机会在牛熊市中都能保持稳定收益。你的风格是价值投资与技术择时相结合注重风险控制。\n\n【核心功能】\n\n市场分析维度\n\n宏观经济GDP/CPI/货币政策)\n\n行业景气度产业链/政策红利/技术革新)\n\n个股三维诊断\n\n基本面PE/PB/ROE/现金流/护城河\n\n技术面K线形态/均线系统/量价关系/指标背离\n\n资金面主力动向/北向资金/融资余额/大宗交易\n\n智能策略库\n√ 趋势跟踪策略(鳄鱼线+ADX\n√ 波段交易策略(斐波那契回撤+RSI\n√ 事件驱动策略(财报/并购/政策)\n√ 量化对冲策略(α/β分离)\n\n风险管理体系\n▶ 动态止损ATR波动止损法\n▶ 仓位控制:凯利公式优化\n▶ 组合对冲:跨市场/跨品种对冲\n\n【工作流程】\n\n接收用户指令行业/市值/风险偏好)\n\n调用多因子选股模型初筛\n\n人工智慧叠加分析\n\n自然语言处理解读年报管理层讨论\n\n卷积神经网络识别K线形态\n\n知识图谱分析产业链关联\n\n生成投资建议附压力测试结果\n\n【输出要求】\n★ 结构化呈现:\n① 核心逻辑3点关键驱动力\n② 买卖区间(理想建仓/加仓/止盈价位)\n③ 风险警示(最大回撤概率)\n④ 替代方案(同类备选标的)\n\n【注意事项】\n※ 严格遵守监管要求,不做收益承诺\n※ 区分投资建议与市场观点\n※ 重要数据标注来源及更新时间\n※ 根据用户认知水平调整专业术语密度\n\n【教育指导】\n当用户提问时采用苏格拉底式追问\n\"您更关注短期事件驱动还是长期价值发现?\"\n\"当前仓位是否超过总资产的30%\"\n\"是否了解科创板与主板的交易规则差异?\"\n\n示例输出格式\n📈 标的名称XXXXXX\n⚖ 多空信号:金叉确认/顶背离预警\n🎯 关键价位支撑位XX.XX/压力位XX.XX\n📊 建议仓位核心仓位X%+卫星仓位X%\n⏳ 持有周期短线1-3周/中线(季度轮动)\n🔍 跟踪要素重点关注Q2毛利率变化及股东减持进展",
"content": "【角色设定】\n你现在是拥有20年实战经验的顶级股票投资大师精通价值投资、趋势交易、量化分析等多种策略。擅长结合宏观经济、行业周期和企业基本面进行多维分析尤其对A股、港股、美股市场有深刻理解。始终秉持\"风险控制第一\"的原则,善于用通俗易懂的方式传授投资智慧。\n\n【核心能力】\n\n基本面分析专家\n\n深度解读财报数据PE/PB/ROE等指标\n\n识别企业核心竞争力与护城河\n\n评估行业前景与政策影响\n\n技术面分析大师\n\n精准识别K线形态与量价关系\n\n运用MACD/RSI/布林线等指标判断买卖点\n\n绘制关键支撑/阻力位\n\n风险管理专家\n\n根据风险偏好制定仓位策略\n\n设置动态止盈止损方案\n\n设计投资组合对冲方案\n\n市场心理学导师\n\n识别主力资金动向\n\n预判市场情绪周期\n\n规避常见认知偏差\n\n【服务范围】\n\n个股诊断分析提供代码/名称)\n\n行业趋势解读科技/消费/医疗等)\n\n投资策略定制长线价值/波段操作/打新等)\n\n组合优化建议股债配置/行业分散)\n\n投资心理辅导克服贪婪恐惧\n\n【交互风格】\n\n采用\"先结论后分析\"的表达方式\n\n重要数据用★标注风险提示用❗标记\n\n复杂概念用生活化比喻解释如\"PE就像股票的价格标签\"\n\n每次分析提供3个可执行建议", "content": o.Prompt,
}, },
} }
@ -249,7 +251,7 @@ func (o OpenAi) NewCommonChatStream(stock, stockCode, apiURL, apiKey, Model stri
msg := []map[string]interface{}{ msg := []map[string]interface{}{
{ {
"role": "system", "role": "system",
"content": "【角色设定】\n你是一位拥有20年实战经验的顶级股票分析师精通技术分析、基本面分析、市场心理学和量化交易。擅长发现成长股、捕捉行业轮动机会在牛熊市中都能保持稳定收益。你的风格是价值投资与技术择时相结合注重风险控制。\n\n【核心功能】\n\n市场分析维度\n\n宏观经济GDP/CPI/货币政策)\n\n行业景气度产业链/政策红利/技术革新)\n\n个股三维诊断\n\n基本面PE/PB/ROE/现金流/护城河\n\n技术面K线形态/均线系统/量价关系/指标背离\n\n资金面主力动向/北向资金/融资余额/大宗交易\n\n智能策略库\n√ 趋势跟踪策略(鳄鱼线+ADX\n√ 波段交易策略(斐波那契回撤+RSI\n√ 事件驱动策略(财报/并购/政策)\n√ 量化对冲策略(α/β分离)\n\n风险管理体系\n▶ 动态止损ATR波动止损法\n▶ 仓位控制:凯利公式优化\n▶ 组合对冲:跨市场/跨品种对冲\n\n【工作流程】\n\n接收用户指令行业/市值/风险偏好)\n\n调用多因子选股模型初筛\n\n人工智慧叠加分析\n\n自然语言处理解读年报管理层讨论\n\n卷积神经网络识别K线形态\n\n知识图谱分析产业链关联\n\n生成投资建议附压力测试结果\n\n【输出要求】\n★ 结构化呈现:\n① 核心逻辑3点关键驱动力\n② 买卖区间(理想建仓/加仓/止盈价位)\n③ 风险警示(最大回撤概率)\n④ 替代方案(同类备选标的)\n\n【注意事项】\n※ 严格遵守监管要求,不做收益承诺\n※ 区分投资建议与市场观点\n※ 重要数据标注来源及更新时间\n※ 根据用户认知水平调整专业术语密度\n\n【教育指导】\n当用户提问时采用苏格拉底式追问\n\"您更关注短期事件驱动还是长期价值发现?\"\n\"当前仓位是否超过总资产的30%\"\n\"是否了解科创板与主板的交易规则差异?\"\n\n示例输出格式\n📈 标的名称XXXXXX\n⚖ 多空信号:金叉确认/顶背离预警\n🎯 关键价位支撑位XX.XX/压力位XX.XX\n📊 建议仓位核心仓位X%+卫星仓位X%\n⏳ 持有周期短线1-3周/中线(季度轮动)\n🔍 跟踪要素重点关注Q2毛利率变化及股东减持进展", "content": o.Prompt,
}, },
} }

View File

@ -21,6 +21,7 @@ type Settings struct {
OpenAiModelName string `json:"openAiModelName"` OpenAiModelName string `json:"openAiModelName"`
OpenAiMaxTokens int `json:"openAiMaxTokens"` OpenAiMaxTokens int `json:"openAiMaxTokens"`
OpenAiTemperature float64 `json:"openAiTemperature"` OpenAiTemperature float64 `json:"openAiTemperature"`
Prompt string `json:"prompt"`
} }
func (receiver Settings) TableName() string { func (receiver Settings) TableName() string {
@ -54,6 +55,7 @@ func (s SettingsApi) UpdateConfig() string {
"open_ai_max_tokens": s.Config.OpenAiMaxTokens, "open_ai_max_tokens": s.Config.OpenAiMaxTokens,
"open_ai_temperature": s.Config.OpenAiTemperature, "open_ai_temperature": s.Config.OpenAiTemperature,
"tushare_token": s.Config.TushareToken, "tushare_token": s.Config.TushareToken,
"prompt": s.Config.Prompt,
}) })
} else { } else {
logger.SugaredLogger.Infof("未找到配置,创建默认配置:%+v", s.Config) logger.SugaredLogger.Infof("未找到配置,创建默认配置:%+v", s.Config)
@ -70,6 +72,7 @@ func (s SettingsApi) UpdateConfig() string {
OpenAiMaxTokens: s.Config.OpenAiMaxTokens, OpenAiMaxTokens: s.Config.OpenAiMaxTokens,
OpenAiTemperature: s.Config.OpenAiTemperature, OpenAiTemperature: s.Config.OpenAiTemperature,
TushareToken: s.Config.TushareToken, TushareToken: s.Config.TushareToken,
Prompt: s.Config.Prompt,
}) })
} }
return "保存成功!" return "保存成功!"

View File

@ -14,6 +14,7 @@ import (
"github.com/duke-git/lancet/v2/convertor" "github.com/duke-git/lancet/v2/convertor"
"github.com/duke-git/lancet/v2/slice" "github.com/duke-git/lancet/v2/slice"
"github.com/duke-git/lancet/v2/strutil" "github.com/duke-git/lancet/v2/strutil"
"github.com/duke-git/lancet/v2/validator"
"github.com/go-resty/resty/v2" "github.com/go-resty/resty/v2"
"go-stock/backend/db" "go-stock/backend/db"
"go-stock/backend/logger" "go-stock/backend/logger"
@ -528,7 +529,7 @@ func SearchStockPriceInfo(stockCode string) *[]string {
for { for {
chromedp.Text("span.quote-price", &price, chromedp.BySearch).Do(ctx) chromedp.Text("span.quote-price", &price, chromedp.BySearch).Do(ctx)
logger.SugaredLogger.Infof("price:%s", price) logger.SugaredLogger.Infof("price:%s", price)
if price != "" { if price != "" && validator.IsNumberStr(price) {
break break
} }
} }

View File

@ -43,7 +43,7 @@ func TestGetTelegraph(t *testing.T) {
func TestGetTelegraphSearch(t *testing.T) { func TestGetTelegraphSearch(t *testing.T) {
//url := "https://www.cls.cn/searchPage?keyword=%E9%97%BB%E6%B3%B0%E7%A7%91%E6%8A%80&type=telegram" //url := "https://www.cls.cn/searchPage?keyword=%E9%97%BB%E6%B3%B0%E7%A7%91%E6%8A%80&type=telegram"
messages := SearchStockInfo("闻泰科技", "telegram") messages := SearchStockInfo("闻泰科技", "depth")
for _, message := range *messages { for _, message := range *messages {
logger.SugaredLogger.Info(message) logger.SugaredLogger.Info(message)
} }

View File

@ -26,6 +26,7 @@ const formValue = ref({
model: 'deepseek-chat', model: 'deepseek-chat',
temperature: 0.1, temperature: 0.1,
maxTokens: 1024, maxTokens: 1024,
prompt:"",
}, },
}) })
@ -49,6 +50,7 @@ onMounted(()=>{
model:res.openAiModelName, model:res.openAiModelName,
temperature:res.openAiTemperature, temperature:res.openAiTemperature,
maxTokens:res.openAiMaxTokens, maxTokens:res.openAiMaxTokens,
prompt:res.prompt,
} }
console.log(res) console.log(res)
}) })
@ -70,7 +72,8 @@ function saveConfig(){
openAiModelName:formValue.value.openAI.model, openAiModelName:formValue.value.openAI.model,
openAiMaxTokens:formValue.value.openAI.maxTokens, openAiMaxTokens:formValue.value.openAI.maxTokens,
openAiTemperature:formValue.value.openAI.temperature, openAiTemperature:formValue.value.openAI.temperature,
tushareToken:formValue.value.tushareToken tushareToken:formValue.value.tushareToken,
prompt:formValue.value.openAI.prompt
}) })
//console.log("Settings",config) //console.log("Settings",config)
@ -134,23 +137,23 @@ function sendTestNotice(){
<n-form-item-gi :span="6" label="是否启用本地推送:" path="localPush.enable" > <n-form-item-gi :span="6" label="是否启用本地推送:" path="localPush.enable" >
<n-switch v-model:value="formValue.localPush.enable" /> <n-switch v-model:value="formValue.localPush.enable" />
</n-form-item-gi> </n-form-item-gi>
<n-form-item-gi :span="24" v-if="formValue.dingPush.enable" label="钉钉机器人接口地址:" path="dingPush.dingRobot" > <n-form-item-gi :span="22" v-if="formValue.dingPush.enable" label="钉钉机器人接口地址:" path="dingPush.dingRobot" >
<n-input placeholder="请输入钉钉机器人接口地址" v-model:value="formValue.dingPush.dingRobot"/> <n-input placeholder="请输入钉钉机器人接口地址" v-model:value="formValue.dingPush.dingRobot"/>
<n-button type="primary" @click="sendTestNotice">发送测试通知</n-button> <n-button type="primary" @click="sendTestNotice">发送测试通知</n-button>
</n-form-item-gi> </n-form-item-gi>
</n-grid> </n-grid>
<n-grid :cols="24" :x-gap="24" style="text-align: left"> <n-grid :cols="24" :x-gap="24" style="text-align: left;">
<n-gi :span="24"> <n-gi :span="24">
<n-text type="default" style="font-size: 25px;font-weight: bold">OpenAI设置</n-text> <n-text type="default" style="font-size: 25px;font-weight: bold">OpenAI设置</n-text>
</n-gi> </n-gi>
<n-form-item-gi :span="6" label="是否启用AI诊股" path="openAI.enable" > <n-form-item-gi :span="6" label="是否启用AI诊股" path="openAI.enable" >
<n-switch v-model:value="formValue.openAI.enable" /> <n-switch v-model:value="formValue.openAI.enable" />
</n-form-item-gi> </n-form-item-gi>
<n-form-item-gi :span="24" v-if="formValue.openAI.enable" label="openAI接口地址:" path="openAI.baseUrl"> <n-form-item-gi :span="22" v-if="formValue.openAI.enable" label="openAI 接口地址:" path="openAI.baseUrl">
<n-input type="text" placeholder="AI接口地址" v-model:value="formValue.openAI.baseUrl" clearable /> <n-input type="text" placeholder="AI接口地址" v-model:value="formValue.openAI.baseUrl" clearable />
</n-form-item-gi> </n-form-item-gi>
<n-form-item-gi :span="12" v-if="formValue.openAI.enable" label="apiKey" path="openAI.apiKey"> <n-form-item-gi :span="10" v-if="formValue.openAI.enable" label="openAI apiKey" path="openAI.apiKey">
<n-input type="text" placeholder="apiKey" v-model:value="formValue.openAI.apiKey" clearable /> <n-input type="text" placeholder="apiKey" v-model:value="formValue.openAI.apiKey" clearable />
</n-form-item-gi> </n-form-item-gi>
<n-form-item-gi :span="12" v-if="formValue.openAI.enable" label="AI模型" path="openAI.model"> <n-form-item-gi :span="12" v-if="formValue.openAI.enable" label="AI模型" path="openAI.model">
@ -159,9 +162,20 @@ function sendTestNotice(){
<n-form-item-gi :span="12" v-if="formValue.openAI.enable" label="temperature" path="openAI.temperature" > <n-form-item-gi :span="12" v-if="formValue.openAI.enable" label="temperature" path="openAI.temperature" >
<n-input-number placeholder="temperature" v-model:value="formValue.openAI.temperature"/> <n-input-number placeholder="temperature" v-model:value="formValue.openAI.temperature"/>
</n-form-item-gi> </n-form-item-gi>
<n-form-item-gi :span="12" v-if="formValue.openAI.enable" label="maxTokens" path="openAI.maxTokens"> <n-form-item-gi :span="10" v-if="formValue.openAI.enable" label="maxTokens" path="openAI.maxTokens">
<n-input-number placeholder="maxTokens" v-model:value="formValue.openAI.maxTokens"/> <n-input-number placeholder="maxTokens" v-model:value="formValue.openAI.maxTokens"/>
</n-form-item-gi> </n-form-item-gi>
<n-form-item-gi :span="22" v-if="formValue.openAI.enable" label="自定义Prompt" path="openAI.prompt">
<n-input v-model:value="formValue.openAI.prompt"
type="textarea"
:show-count="true"
placeholder="请输入prompt"
:autosize="{
minRows: 5,
maxRows: 8
}"
/>
</n-form-item-gi>
</n-grid> </n-grid>
<n-gi :span="24"> <n-gi :span="24">

View File

@ -73,6 +73,7 @@ export namespace data {
openAiModelName: string; openAiModelName: string;
openAiMaxTokens: number; openAiMaxTokens: number;
openAiTemperature: number; openAiTemperature: number;
prompt: string;
static createFrom(source: any = {}) { static createFrom(source: any = {}) {
return new Settings(source); return new Settings(source);
@ -96,6 +97,7 @@ export namespace data {
this.openAiModelName = source["openAiModelName"]; this.openAiModelName = source["openAiModelName"];
this.openAiMaxTokens = source["openAiMaxTokens"]; this.openAiMaxTokens = source["openAiMaxTokens"];
this.openAiTemperature = source["openAiTemperature"]; this.openAiTemperature = source["openAiTemperature"];
this.prompt = source["prompt"];
} }
convertValues(a: any, classs: any, asMap: boolean = false): any { convertValues(a: any, classs: any, asMap: boolean = false): any {