Compare commits
11 Commits
v2025.9.27
...
v2025.10.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1d81fdba87 | ||
|
|
6aca0e15cc | ||
|
|
173ce6f243 | ||
|
|
e7875e73d3 | ||
|
|
ca4727db80 | ||
|
|
84ffe7c5fd | ||
|
|
da02d1bd1c | ||
|
|
bae2bf9c5c | ||
|
|
6568b5949a | ||
|
|
c4287f9b78 | ||
|
|
87441d8923 |
@@ -62,7 +62,7 @@
|
||||
| 功能说明 | 状态 | 备注 |
|
||||
|-----------------|----|----------------------------------------------------------------------------------------------------------|
|
||||
| 股票分析知识库 | 🚧 | 未来计划 |
|
||||
| Ai智能选股 | 🚧 | Ai智能选股功能开发中(下半年重点开发计划) |
|
||||
| Ai智能选股 | ✅ | Ai智能选股功能(市场行情-》AI总结/AI智能体功能) |
|
||||
| ETF支持 | 🚧 | ETF数据支持 (目前可以查看净值和估值) |
|
||||
| 美股支持 | ✅ | 美股数据支持 |
|
||||
| 港股支持 | ✅ | 港股数据支持 |
|
||||
|
||||
44
app.go
44
app.go
@@ -131,6 +131,50 @@ func AddTools(tools []data.Tool) []data.Tool {
|
||||
},
|
||||
})
|
||||
|
||||
//tools = append(tools, data.Tool{
|
||||
// Type: "function",
|
||||
// Function: data.ToolFunction{
|
||||
// Name: "QueryBKDictInfo",
|
||||
// Description: "获取所有板块/行业名称或者代码(bkCode,bkName)",
|
||||
// },
|
||||
//})
|
||||
|
||||
//tools = append(tools, data.Tool{
|
||||
// Type: "function",
|
||||
// Function: data.ToolFunction{
|
||||
// Name: "GetIndustryResearchReport",
|
||||
// Description: "获取行业/板块研究报告,请先使用QueryBKDictInfo工具获取行业代码,然后输入行业代码调用",
|
||||
// Parameters: data.FunctionParameters{
|
||||
// Type: "object",
|
||||
// Properties: map[string]any{
|
||||
// "bkCode": map[string]any{
|
||||
// "type": "string",
|
||||
// "description": "板块/行业代码",
|
||||
// },
|
||||
// },
|
||||
// Required: []string{"bkCode"},
|
||||
// },
|
||||
// },
|
||||
//})
|
||||
|
||||
tools = append(tools, data.Tool{
|
||||
Type: "function",
|
||||
Function: data.ToolFunction{
|
||||
Name: "GetStockResearchReport",
|
||||
Description: "获取股票的分析/研究报告",
|
||||
Parameters: data.FunctionParameters{
|
||||
Type: "object",
|
||||
Properties: map[string]any{
|
||||
"stockCode": map[string]any{
|
||||
"type": "string",
|
||||
"description": "股票代码",
|
||||
},
|
||||
},
|
||||
Required: []string{"stockCode"},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
return tools
|
||||
}
|
||||
|
||||
|
||||
@@ -73,6 +73,7 @@ func GetStockAiAgent(ctx *context.Context, aiConfig data.AIConfig) *react.Agent
|
||||
tools.GetFinancialReportTool(),
|
||||
tools.GetQueryStockNewsTool(),
|
||||
tools.GetIndustryResearchReportTool(),
|
||||
tools.GetQueryBKDictTool(),
|
||||
},
|
||||
}
|
||||
// 创建 agent
|
||||
|
||||
34
backend/agent/tools/bk_dict_tool.go
Normal file
34
backend/agent/tools/bk_dict_tool.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"go-stock/backend/data"
|
||||
|
||||
"github.com/cloudwego/eino/components/tool"
|
||||
"github.com/cloudwego/eino/schema"
|
||||
"github.com/coocood/freecache"
|
||||
)
|
||||
|
||||
// @Author spark
|
||||
// @Date 2025/9/27 14:09
|
||||
// @Desc
|
||||
// -----------------------------------------------------------------------------------
|
||||
type ToolQueryBKDict struct{}
|
||||
|
||||
func (t ToolQueryBKDict) Info(ctx context.Context) (*schema.ToolInfo, error) {
|
||||
return &schema.ToolInfo{
|
||||
Name: "QueryBKDictInfo",
|
||||
Desc: "获取所有板块/行业名称或者代码(bkCode,bkName)",
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (t ToolQueryBKDict) InvokableRun(ctx context.Context, argumentsInJSON string, opts ...tool.Option) (string, error) {
|
||||
resp := data.NewMarketNewsApi().EMDictCode("016", freecache.NewCache(100))
|
||||
bytes, err := json.Marshal(resp)
|
||||
return string(bytes), err
|
||||
}
|
||||
|
||||
func GetQueryBKDictTool() tool.InvokableTool {
|
||||
return &ToolQueryBKDict{}
|
||||
}
|
||||
@@ -117,6 +117,27 @@ func (m MarketNewsApi) GetNewsList(source string, limit int) *[]*models.Telegrap
|
||||
}
|
||||
return news
|
||||
}
|
||||
func (m MarketNewsApi) GetNewsList2(source string, limit int) *[]*models.Telegraph {
|
||||
news := &[]*models.Telegraph{}
|
||||
if source != "" {
|
||||
db.Dao.Model(news).Preload("TelegraphTags").Where("source=?", source).Order("id desc,is_red desc").Limit(limit).Find(news)
|
||||
} else {
|
||||
db.Dao.Model(news).Preload("TelegraphTags").Order("id desc,is_red desc").Limit(limit).Find(news)
|
||||
}
|
||||
for _, item := range *news {
|
||||
tags := &[]models.Tags{}
|
||||
db.Dao.Model(&models.Tags{}).Where("id in ?", lo.Map(item.TelegraphTags, func(item models.TelegraphTags, index int) uint {
|
||||
return item.TagId
|
||||
})).Find(&tags)
|
||||
tagNames := lo.Map(*tags, func(item models.Tags, index int) string {
|
||||
return item.Name
|
||||
})
|
||||
item.SubjectTags = tagNames
|
||||
logger.SugaredLogger.Infof("tagNames %v ,SubjectTags:%s", tagNames, item.SubjectTags)
|
||||
}
|
||||
return news
|
||||
}
|
||||
|
||||
func (m MarketNewsApi) GetTelegraphList(source string) *[]*models.Telegraph {
|
||||
news := &[]*models.Telegraph{}
|
||||
if source != "" {
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"encoding/json"
|
||||
"go-stock/backend/db"
|
||||
"go-stock/backend/logger"
|
||||
"go-stock/backend/models"
|
||||
"go-stock/backend/util"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -72,9 +73,12 @@ func TestLongTiger(t *testing.T) {
|
||||
|
||||
func TestStockResearchReport(t *testing.T) {
|
||||
db.Init("../../data/stock.db")
|
||||
resp := NewMarketNewsApi().StockResearchReport("600584.sh", 7)
|
||||
resp := NewMarketNewsApi().StockResearchReport("688082", 7)
|
||||
for _, a := range resp {
|
||||
logger.SugaredLogger.Debugf("value: %+v", a)
|
||||
data := a.(map[string]any)
|
||||
logger.SugaredLogger.Debugf("value: %s infoCode:%s", data["title"], data["infoCode"])
|
||||
NewMarketNewsApi().GetIndustryReportInfo(data["infoCode"].(string))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,7 +112,11 @@ func TestEMDictCode(t *testing.T) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
dict := &[]models.BKDict{}
|
||||
json.Unmarshal(bytes, dict)
|
||||
logger.SugaredLogger.Debugf("value: %s", string(bytes))
|
||||
md := util.MarkdownTableWithTitle("行业/板块代码", dict)
|
||||
logger.SugaredLogger.Debugf(md)
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -324,7 +324,7 @@ func (o *OpenAi) NewSummaryStockNewsStreamWithTools(userQuestion string, sysProm
|
||||
}()
|
||||
wg.Wait()
|
||||
|
||||
news := NewMarketNewsApi().GetNewsList("财联社电报", random.RandInt(100, 500))
|
||||
news := NewMarketNewsApi().GetNewsList2("财联社电报", random.RandInt(100, 500))
|
||||
messageText := strings.Builder{}
|
||||
for _, telegraph := range *news {
|
||||
messageText.WriteString("## " + telegraph.Time + ":" + "\n")
|
||||
@@ -473,7 +473,7 @@ func (o *OpenAi) NewSummaryStockNewsStream(userQuestion string, sysPromptId *int
|
||||
|
||||
wg.Wait()
|
||||
|
||||
news := NewMarketNewsApi().GetNewsList("", 100)
|
||||
news := NewMarketNewsApi().GetNewsList2("财联社电报", random.RandInt(100, 500))
|
||||
messageText := strings.Builder{}
|
||||
for _, telegraph := range *news {
|
||||
messageText.WriteString("## " + telegraph.Time + ":" + "\n")
|
||||
@@ -1366,6 +1366,140 @@ func AskAiWithTools(o *OpenAi, err error, messages []map[string]interface{}, ch
|
||||
"tool_call_id": currentCallId,
|
||||
})
|
||||
}
|
||||
//
|
||||
//if funcName == "QueryBKDictInfo" {
|
||||
// ch <- map[string]any{
|
||||
// "code": 1,
|
||||
// "question": question,
|
||||
// "chatId": streamResponse.Id,
|
||||
// "model": streamResponse.Model,
|
||||
// "content": "\r\n```\r\n开始调用工具:QueryBKDictInfo,\n参数:" + funcArguments + "\r\n```\r\n",
|
||||
// "time": time.Now().Format(time.DateTime),
|
||||
// }
|
||||
// res := NewMarketNewsApi().EMDictCode("016", freecache.NewCache(100))
|
||||
// bytes, err := json.Marshal(res)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// dict := &[]models.BKDict{}
|
||||
// json.Unmarshal(bytes, dict)
|
||||
// md := util.MarkdownTableWithTitle("行业/板块代码", dict)
|
||||
// logger.SugaredLogger.Infof("行业/板块代码=\n%s", md)
|
||||
// messages = append(messages, map[string]interface{}{
|
||||
// "role": "assistant",
|
||||
// "content": currentAIContent.String(),
|
||||
// "tool_calls": []map[string]any{
|
||||
// {
|
||||
// "id": currentCallId,
|
||||
// "tool_call_id": currentCallId,
|
||||
// "type": "function",
|
||||
// "function": map[string]string{
|
||||
// "name": funcName,
|
||||
// "arguments": funcArguments,
|
||||
// "parameters": funcArguments,
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// })
|
||||
// messages = append(messages, map[string]interface{}{
|
||||
// "role": "tool",
|
||||
// "content": md,
|
||||
// "tool_call_id": currentCallId,
|
||||
// })
|
||||
//}
|
||||
|
||||
//if funcName == "GetIndustryResearchReport" {
|
||||
// bkCode := gjson.Get(funcArguments, "bkCode").String()
|
||||
// ch <- map[string]any{
|
||||
// "code": 1,
|
||||
// "question": question,
|
||||
// "chatId": streamResponse.Id,
|
||||
// "model": streamResponse.Model,
|
||||
// "content": "\r\n```\r\n开始调用工具:GetIndustryResearchReport,\n参数:" + bkCode + "\r\n```\r\n",
|
||||
// "time": time.Now().Format(time.DateTime),
|
||||
// }
|
||||
// bkCode = strutil.ReplaceWithMap(bkCode, map[string]string{
|
||||
// "-": "",
|
||||
// "_": "",
|
||||
// "bk": "",
|
||||
// "BK": "",
|
||||
// "bk0": "",
|
||||
// "BK0": "",
|
||||
// })
|
||||
//
|
||||
// logger.SugaredLogger.Debugf("code:%s", bkCode)
|
||||
// codeStr := convertor.ToString(bkCode)
|
||||
// res := NewMarketNewsApi().IndustryResearchReport(codeStr, 7)
|
||||
// md := strings.Builder{}
|
||||
// for _, a := range res {
|
||||
// d := a.(map[string]any)
|
||||
// md.WriteString(NewMarketNewsApi().GetIndustryReportInfo(d["infoCode"].(string)))
|
||||
// }
|
||||
// logger.SugaredLogger.Infof("bkCode:%s IndustryResearchReport:\n %s", bkCode, md.String())
|
||||
// messages = append(messages, map[string]interface{}{
|
||||
// "role": "assistant",
|
||||
// "content": currentAIContent.String(),
|
||||
// "tool_calls": []map[string]any{
|
||||
// {
|
||||
// "id": currentCallId,
|
||||
// "tool_call_id": currentCallId,
|
||||
// "type": "function",
|
||||
// "function": map[string]string{
|
||||
// "name": funcName,
|
||||
// "arguments": funcArguments,
|
||||
// "parameters": funcArguments,
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// })
|
||||
// messages = append(messages, map[string]interface{}{
|
||||
// "role": "tool",
|
||||
// "content": md.String(),
|
||||
// "tool_call_id": currentCallId,
|
||||
// })
|
||||
//}
|
||||
|
||||
if funcName == "GetStockResearchReport" {
|
||||
stockCode := gjson.Get(funcArguments, "stockCode").String()
|
||||
ch <- map[string]any{
|
||||
"code": 1,
|
||||
"question": question,
|
||||
"chatId": streamResponse.Id,
|
||||
"model": streamResponse.Model,
|
||||
"content": "\r\n```\r\n开始调用工具:GetStockResearchReport,\n参数:" + stockCode + "\r\n```\r\n",
|
||||
"time": time.Now().Format(time.DateTime),
|
||||
}
|
||||
res := NewMarketNewsApi().StockResearchReport(stockCode, 7)
|
||||
md := strings.Builder{}
|
||||
for _, a := range res {
|
||||
logger.SugaredLogger.Debugf("value: %+v", a)
|
||||
d := a.(map[string]any)
|
||||
logger.SugaredLogger.Debugf("value: %s infoCode:%s", d["title"], d["infoCode"])
|
||||
md.WriteString(NewMarketNewsApi().GetIndustryReportInfo(d["infoCode"].(string)))
|
||||
}
|
||||
logger.SugaredLogger.Infof("stockCode:%s StockResearchReport:\n %s", stockCode, md.String())
|
||||
messages = append(messages, map[string]interface{}{
|
||||
"role": "assistant",
|
||||
"content": currentAIContent.String(),
|
||||
"tool_calls": []map[string]any{
|
||||
{
|
||||
"id": currentCallId,
|
||||
"tool_call_id": currentCallId,
|
||||
"type": "function",
|
||||
"function": map[string]string{
|
||||
"name": funcName,
|
||||
"arguments": funcArguments,
|
||||
"parameters": funcArguments,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
messages = append(messages, map[string]interface{}{
|
||||
"role": "tool",
|
||||
"content": md.String(),
|
||||
"tool_call_id": currentCallId,
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
AskAiWithTools(o, err, messages, ch, question, tools)
|
||||
|
||||
@@ -3,9 +3,10 @@ package data
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/go-resty/resty/v2"
|
||||
"go-stock/backend/logger"
|
||||
"time"
|
||||
|
||||
"github.com/go-resty/resty/v2"
|
||||
)
|
||||
|
||||
// @Author spark
|
||||
@@ -25,25 +26,25 @@ func (s SearchStockApi) SearchStock(pageSize int) map[string]any {
|
||||
SetHeader("Host", "np-tjxg-g.eastmoney.com").
|
||||
SetHeader("Origin", "https://xuangu.eastmoney.com").
|
||||
SetHeader("Referer", "https://xuangu.eastmoney.com/").
|
||||
SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:140.0) Gecko/20100101 Firefox/140.0").
|
||||
SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:145.0) Gecko/20100101 Firefox/145.0").
|
||||
SetHeader("Content-Type", "application/json").
|
||||
SetBody(fmt.Sprintf(`{
|
||||
"keyWord": "%s",
|
||||
"pageSize": %d,
|
||||
"pageNo": 1,
|
||||
"fingerprint": "e38b5faabf9378c8238e57219f0ebc9b",
|
||||
"fingerprint": "02efa8944b1f90fbfe050e1e695a480d",
|
||||
"gids": [],
|
||||
"matchWord": "",
|
||||
"timestamp": "1751113883290349",
|
||||
"timestamp": "%d",
|
||||
"shareToGuba": false,
|
||||
"requestId": "8xTWgCDAjvQ5lmvz5mDA3Ydk2AE4yoiJ1751113883290",
|
||||
"requestId": "RMd3Y76AJI98axPvdhdbKvbBDVwLlUK61761559950168",
|
||||
"needCorrect": true,
|
||||
"removedConditionIdList": [],
|
||||
"xcId": "xc0af28549ab330013ed",
|
||||
"xcId": "xc0d61279aad33008260",
|
||||
"ownSelectAll": false,
|
||||
"dxInfo": [],
|
||||
"extraCondition": ""
|
||||
}`, s.words, pageSize)).Post(url)
|
||||
}`, s.words, pageSize, time.Now().Unix())).Post(url)
|
||||
if err != nil {
|
||||
logger.SugaredLogger.Errorf("SearchStock-err:%+v", err)
|
||||
return map[string]any{}
|
||||
|
||||
@@ -2,16 +2,18 @@ package data
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/duke-git/lancet/v2/convertor"
|
||||
"go-stock/backend/db"
|
||||
"go-stock/backend/logger"
|
||||
"testing"
|
||||
|
||||
"github.com/duke-git/lancet/v2/convertor"
|
||||
)
|
||||
|
||||
func TestSearchStock(t *testing.T) {
|
||||
db.Init("../../data/stock.db")
|
||||
|
||||
res := NewSearchStockApi("算力股;净利润连续3年增长").SearchStock(10)
|
||||
logger.SugaredLogger.Infof("res:%+v", res)
|
||||
data := res["data"].(map[string]any)
|
||||
result := data["result"].(map[string]any)
|
||||
dataList := result["dataList"].([]any)
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/plugin/soft_delete"
|
||||
"time"
|
||||
)
|
||||
|
||||
// @Author spark
|
||||
@@ -687,3 +688,16 @@ type CailianpressWeb struct {
|
||||
Author string `json:"author" md:"资讯发布者"`
|
||||
} `json:"list"`
|
||||
}
|
||||
|
||||
type BKDict struct {
|
||||
gorm.Model `md:"-"`
|
||||
BkCode string `json:"bkCode" md:"行业/板块代码"`
|
||||
BkName string `json:"bkName" md:"行业/板块名称"`
|
||||
FirstLetter string `json:"firstLetter" md:"first_letter"`
|
||||
FubkCode string `json:"fubkCode" md:"fubk_code"`
|
||||
PublishCode string `json:"publishCode" md:"publish_code"`
|
||||
}
|
||||
|
||||
func (b BKDict) TableName() string {
|
||||
return "bk_dict"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user