Compare commits

...

5 Commits

20 changed files with 956 additions and 7 deletions

6
go.mod
View File

@ -26,6 +26,7 @@ require (
github.com/gofiber/websocket/v2 v2.2.1
github.com/google/uuid v1.6.0
github.com/google/wire v0.7.0
github.com/lukasjarosch/go-docx v0.5.0
github.com/ollama/ollama v0.12.7
github.com/redis/go-redis/v9 v9.16.0
github.com/robfig/cron/v3 v3.0.1
@ -33,6 +34,8 @@ require (
github.com/spf13/viper v1.17.0
github.com/stretchr/testify v1.11.1
github.com/tmc/langchaingo v0.1.13
github.com/unidoc/unioffice v1.39.0
github.com/volcengine/volcengine-go-sdk v1.2.9
github.com/xuri/excelize/v2 v2.10.0
golang.org/x/sync v0.17.0
google.golang.org/grpc v1.64.0
@ -75,6 +78,7 @@ require (
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.17.9 // indirect
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
@ -112,6 +116,7 @@ require (
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.51.0 // indirect
github.com/valyala/tcplisten v1.0.0 // indirect
github.com/volcengine/volc-sdk-golang v1.0.23 // indirect
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
github.com/xuri/efp v0.0.1 // indirect
github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9 // indirect
@ -128,5 +133,6 @@ require (
google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 // indirect
google.golang.org/protobuf v1.34.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

21
go.sum
View File

@ -101,6 +101,7 @@ github.com/aliyun/credentials-go v1.4.6 h1:CG8rc/nxCNKfXbZWpWDzI9GjF4Tuu3Es14qT8
github.com/aliyun/credentials-go v1.4.6/go.mod h1:Jm6d+xIgwJVLVWT561vy67ZRP4lPTQxMbEYRuT2Ti1U=
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
@ -237,6 +238,7 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
@ -248,6 +250,7 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
@ -266,6 +269,7 @@ github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLe
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/wire v0.7.0 h1:JxUKI6+CVBgCO2WToKy/nQk0sS+amI9z9EjVmdaocj4=
@ -293,6 +297,10 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
@ -310,6 +318,7 @@ github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
@ -318,6 +327,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/lukasjarosch/go-docx v0.5.0 h1:4vU+gJ4WMdqwRvRVFF+XMw3rPfUGSXlToPJIX3mHQsQ=
github.com/lukasjarosch/go-docx v0.5.0/go.mod h1:ka/NZgDIJId48vMvcfWfduVTY7uV0/f8EgsmCjuS9X0=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
@ -443,12 +454,18 @@ github.com/tmc/langchaingo v0.1.13 h1:rcpMWBIi2y3B90XxfE4Ao8dhCQPVDMaNPnN5cGB1Ca
github.com/tmc/langchaingo v0.1.13/go.mod h1:vpQ5NOIhpzxDfTZK9B6tf2GM/MoaHewPWM5KXXGh7hg=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/unidoc/unioffice v1.39.0 h1:Wo5zvrzCqhyK/1Zi5dg8a5F5+NRftIMZPnFPYwruLto=
github.com/unidoc/unioffice v1.39.0/go.mod h1:Axz6ltIZZTUUyHoEnPe4Mb3VmsN4TRHT5iZCGZ1rgnU=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA=
github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g=
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
github.com/volcengine/volc-sdk-golang v1.0.23 h1:anOslb2Qp6ywnsbyq9jqR0ljuO63kg9PY+4OehIk5R8=
github.com/volcengine/volc-sdk-golang v1.0.23/go.mod h1:AfG/PZRUkHJ9inETvbjNifTDgut25Wbkm2QoYBTbvyU=
github.com/volcengine/volcengine-go-sdk v1.2.9 h1:du2gnImtyWXKkQFnJW/GXCs+UBibGGOXIbP1Ams2pB8=
github.com/volcengine/volcengine-go-sdk v1.2.9/go.mod h1:oxoVo+A17kvkwPkIeIHPVLjSw7EQAm+l/Vau1YGHN+A=
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
github.com/x-cray/logrus-prefixed-formatter v0.5.2 h1:00txxvfBM9muc0jiLIEAkAcIMJzfthRT6usrui8uGmg=
@ -567,6 +584,7 @@ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200925080053-05aa5d4ee321/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
@ -839,11 +857,14 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/bsm/ratelimit.v1 v1.0.0-20160220154919-db14e161995a/go.mod h1:KF9sEfUPAXdG8Oev9e99iLGnl2uJMjc5B+4y3O7x610=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=

156
internal/biz/advice.go Normal file
View File

@ -0,0 +1,156 @@
package biz
import (
"ai_scheduler/internal/biz/llm_service/third_party"
"ai_scheduler/internal/entitys"
"context"
"encoding/json"
"fmt"
"os"
"strings"
"github.com/volcengine/volcengine-go-sdk/service/arkruntime/model"
"github.com/volcengine/volcengine-go-sdk/volcengine"
)
type AdviceBiz struct {
hsyq *third_party.Hsyq
}
func NewAdviceBiz(hsyq *third_party.Hsyq) *AdviceBiz {
return &AdviceBiz{
hsyq: hsyq,
}
}
const (
key = "236ba4b6-9daa-4755-b22f-2fd274cd223a"
modelName = "doubao-seed-1-8-251228"
)
var dataMap = map[string]string{
"DialectFeatures": (&entitys.DialectFeatures{}).Example(),
"SentencePatterns": (&entitys.SentencePatterns{}).Example(),
"PersonalityTags": (&entitys.PersonalityTags{}).Example(),
"ToneTags": (&entitys.ToneTags{}).Example(),
"SignatureDialogues": (&entitys.SignatureDialogues{}).Example(),
"RegionValue": (&entitys.RegionValue{}).Example(),
"CompetitionComparison": (&entitys.CompetitionComparison{}).Example(),
"CoreSellingPoints": (&entitys.CoreSellingPoints{}).Example(),
"SupportingFacilities": (&entitys.SupportingFacilities{}).Example(),
"DeveloperBacking": (&entitys.DeveloperBacking{}).Example(),
"NeedsMining": (&entitys.NeedsMining{}).Example(),
"PainPointResponse": (&entitys.PainPointResponse{}).Example(),
"ValueBuilding": (&entitys.ValueBuilding{}).Example(),
"ClosingTechniques": (&entitys.ClosingTechniques{}).Example(),
"CommunicationRhythm": (&entitys.CommunicationRhythm{}).Example(),
}
func (a *AdviceBiz) WordAna(ctx context.Context, wordContent string) error {
examples := a.getAllExamples()
prompt := a.buildSimplePrompt(wordContent, examples)
os.WriteFile("requset.json", []byte(prompt), 0644)
anaContent, err := a.callLlm(ctx, prompt)
if err != nil {
return err
}
os.WriteFile("res.json", []byte(anaContent), 0644)
data := a.parseResponse(anaContent)
jsonData, _ := json.MarshalIndent(data, "", " ")
os.WriteFile("extracted.json", jsonData, 0644)
fmt.Println("✅ 数据已保存到 extracted.json")
return nil
}
func (a *AdviceBiz) callLlm(ctx context.Context, prompt string) (string, error) {
var message = make([]*model.ChatCompletionMessage, 1)
message[0] = &model.ChatCompletionMessage{
Role: model.ChatMessageRoleUser,
Content: &model.ChatCompletionMessageContent{
StringValue: volcengine.String(prompt),
},
}
res, err := a.hsyq.RequestHsyq(ctx, key, modelName, message)
if err != nil {
return "", err
}
return *res.Choices[0].Message.Content.StringValue, nil
}
func (a *AdviceBiz) getAllExamples() map[string]string {
return dataMap
}
func (a *AdviceBiz) buildSimplePrompt(wordContent string, examples map[string]string) string {
// 最简单的提示词模板
template := `分析以下房地产销售对话按指定格式提取信息
对话内容
%s
请按照以下` + fmt.Sprintf("%d", len(examples)) + `个格式生成JSON数据key为格式名称value为对应值
%s
输出要求
1. 每个结构体一个JSON对象
2. 所有内容必须严格基于提供的对话原文不得编造
3. 严格按照示例格式
4. 将上述生成的` + fmt.Sprintf("%d", len(examples)) + `个JSON对象,json不需要有可读性,不要有特殊符号比如"\n",用map[string]json来包裹所有json对象{"SupportingFacilities":{...},"SignatureDialogues":[{...},{...}]}`
// 构建格式部分
var formats strings.Builder
for name, example := range examples {
formats.WriteString(fmt.Sprintf("=== %s ===\n示例%s\n\n", name, example))
}
return fmt.Sprintf(template, wordContent, formats.String())
}
func (a *AdviceBiz) parseResponse(response string) map[string]interface{} {
result := make(map[string]interface{})
// 按空行分割
parts := strings.Split(response, "\n\n")
for _, part := range parts {
part = strings.TrimSpace(part)
if part == "" || !strings.Contains(part, "{") {
continue
}
// 找到第一个 { 和最后一个 }
start := strings.Index(part, "{")
end := strings.LastIndex(part, "}")
if start == -1 || end == -1 || end <= start {
continue
}
jsonStr := part[start : end+1]
// 尝试解析
var data interface{}
if err := json.Unmarshal([]byte(jsonStr), &data); err == nil {
// 判断是什么结构体
for _, name := range getStructNames() {
if strings.Contains(jsonStr, `"`+name+`"`) || strings.Contains(part, name) {
result[name] = data
break
}
}
}
}
return result
}
func getStructNames() []string {
var res = make([]string, 0, len(dataMap))
for k, _ := range dataMap {
res = append(res, k)
}
return res
}

View File

@ -0,0 +1,52 @@
package third_party
import (
"context"
"time"
"github.com/volcengine/volcengine-go-sdk/service/arkruntime"
"github.com/volcengine/volcengine-go-sdk/service/arkruntime/model"
)
type Hsyq struct {
mapClient map[string]*arkruntime.Client
}
func NewHsyq() *Hsyq {
return &Hsyq{
mapClient: make(map[string]*arkruntime.Client),
}
}
func (h *Hsyq) getClient(key string) *arkruntime.Client {
var client *arkruntime.Client
if _, ok := h.mapClient[key]; ok {
client = h.mapClient[key]
} else {
client = arkruntime.NewClientWithApiKey(
key,
arkruntime.WithRegion("cn-beijing"),
arkruntime.WithTimeout(2*time.Minute),
arkruntime.WithRetryTimes(2),
)
h.mapClient[key] = client
}
return client
}
// 火山引擎
func (h *Hsyq) RequestHsyq(ctx context.Context, key string, modelName string, prompt []*model.ChatCompletionMessage) (model.ChatCompletionResponse, error) {
req := model.CreateChatCompletionRequest{
Model: modelName,
Messages: prompt,
Stream: new(bool),
Thinking: &model.Thinking{Type: model.ThinkingTypeDisabled},
}
resp, err := h.getClient(key).CreateChatCompletion(ctx, req)
if err != nil {
return model.ChatCompletionResponse{ID: ""}, err
}
return resp, err
}

View File

@ -3,6 +3,7 @@ package biz
import (
"ai_scheduler/internal/biz/do"
"ai_scheduler/internal/biz/llm_service"
"ai_scheduler/internal/biz/llm_service/third_party"
"github.com/google/wire"
)
@ -21,4 +22,6 @@ var ProviderSetBiz = wire.NewSet(
NewQywxAppBiz,
NewGroupConfigBiz,
do.NewMacro,
NewAdviceBiz,
third_party.NewHsyq,
)

View File

@ -0,0 +1,18 @@
package impl
import (
"ai_scheduler/internal/data/model"
"ai_scheduler/tmpl/dataTemp"
"ai_scheduler/utils"
)
type AdviceAdvicerImpl struct {
dataTemp.DataTemp
BaseRepository[model.AiTask]
}
func NewAdviceAdvicerImplImpl(db *utils.Db) *AdviceAdvicerImpl {
return &AdviceAdvicerImpl{
DataTemp: *dataTemp.NewDataTemp(db, new(model.AiAdviceAdvicer)),
}
}

View File

@ -0,0 +1,18 @@
package impl
import (
"ai_scheduler/internal/data/model"
"ai_scheduler/tmpl/dataTemp"
"ai_scheduler/utils"
)
type AdviceProjectImpl struct {
dataTemp.DataTemp
BaseRepository[model.AiTask]
}
func NewAdviceProjectImpl(db *utils.Db) *AdviceProjectImpl {
return &AdviceProjectImpl{
DataTemp: *dataTemp.NewDataTemp(db, new(model.AiAdviceProject)),
}
}

View File

@ -0,0 +1,18 @@
package impl
import (
"ai_scheduler/internal/data/model"
"ai_scheduler/tmpl/dataTemp"
"ai_scheduler/utils"
)
type AdviceTalkImpl struct {
dataTemp.DataTemp
BaseRepository[model.AiTask]
}
func NewAdviceTalkImpl(db *utils.Db) *AdviceTalkImpl {
return &AdviceTalkImpl{
DataTemp: *dataTemp.NewDataTemp(db, new(model.AiAdviceTalk)),
}
}

View File

@ -18,4 +18,7 @@ var ProviderImpl = wire.NewSet(
NewBotGroupConfigImpl,
NewBotGroupQywxImpl,
NewReportDailyCacheImpl,
NewAdviceAdvicerImplImpl,
NewAdviceProjectImpl,
NewAdviceTalkImpl,
)

View File

@ -0,0 +1,33 @@
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
package model
import (
"time"
)
const TableNameAiAdviceAdvicer = "ai_advice_advicer"
// AiAdviceAdvicer mapped from table <ai_advice_advicer>
type AiAdviceAdvicer struct {
AdvicerID int32 `gorm:"column:advicer_id;primaryKey;autoIncrement:true" json:"advicer_id"`
Name string `gorm:"column:name;not null;comment:姓名" json:"name"` // 姓名
Birth time.Time `gorm:"column:birth;not null;comment:用户名称" json:"birth"` // 用户名称
Gender int32 `gorm:"column:gender;not null;comment:1:男2女" json:"gender"` // 1:男2
WorkingYears int32 `gorm:"column:working_years;not null;default:1;comment:工作年限" json:"working_years"` // 工作年限
ContactTags string `gorm:"column:contact_tags;not null;comment:联系方式" json:"contact_tags"` // 联系方式
NativeRegion string `gorm:"column:native_region;not null;comment:籍贯" json:"native_region"` // 籍贯
DialectFeatures string `gorm:"column:dialect_features;not null;comment:语言风格" json:"dialect_features"` // 语言风格
SentencePatterns string `gorm:"column:sentence_patterns;comment:句子模式" json:"sentence_patterns"` // 句子模式
ToneTags string `gorm:"column:tone_tags;comment:语气标签" json:"tone_tags"` // 语气标签
PersonalityTags string `gorm:"column:personality_tags;not null;comment:个性标签" json:"personality_tags"` // 个性标签
SignatureDialogues string `gorm:"column:signature_dialogues;comment:代表性对话示例" json:"signature_dialogues"` // 代表性对话示例
CreateAt time.Time `gorm:"column:create_at;default:CURRENT_TIMESTAMP" json:"create_at"`
}
// TableName AiAdviceAdvicer's table name
func (*AiAdviceAdvicer) TableName() string {
return TableNameAiAdviceAdvicer
}

View File

@ -0,0 +1,28 @@
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
package model
import (
"time"
)
const TableNameAiAdviceProject = "ai_advice_project"
// AiAdviceProject mapped from table <ai_advice_project>
type AiAdviceProject struct {
ProjectID int32 `gorm:"column:project_id;primaryKey;autoIncrement:true" json:"project_id"`
Name string `gorm:"column:name;not null;comment:姓名" json:"name"` // 姓名
RegionValue string `gorm:"column:region_value;comment:区域价值话术库" json:"region_value"` // 区域价值话术库
CompetitionComparison string `gorm:"column:competition_comparison;comment:竞品对比话术" json:"competition_comparison"` // 竞品对比话术
CoreSellingPoints string `gorm:"column:core_selling_points;comment:项目核心卖点" json:"core_selling_points"` // 项目核心卖点
SupportingFacilities string `gorm:"column:supporting_facilities;comment:配套体系" json:"supporting_facilities"` // 配套体系
DeveloperBacking string `gorm:"column:developer_backing;comment:开发商背书" json:"developer_backing"` // 开发商背书
CreateAt time.Time `gorm:"column:create_at;default:CURRENT_TIMESTAMP" json:"create_at"`
}
// TableName AiAdviceProject's table name
func (*AiAdviceProject) TableName() string {
return TableNameAiAdviceProject
}

View File

@ -0,0 +1,27 @@
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
package model
import (
"time"
)
const TableNameAiAdviceTalk = "ai_advice_talk"
// AiAdviceTalk mapped from table <ai_advice_talk>
type AiAdviceTalk struct {
TalkID int32 `gorm:"column:talk_id;primaryKey;autoIncrement:true" json:"talk_id"`
NeedsMining string `gorm:"column:needs_mining;comment:需求挖掘话术" json:"needs_mining"` // 需求挖掘话术
PainPointResponse string `gorm:"column:pain_point_response;comment:痛点应对策略" json:"pain_point_response"` // 痛点应对策略
ValueBuilding string `gorm:"column:value_building;comment:价值塑造技巧" json:"value_building"` // 价值塑造技巧
ClosingTechniques string `gorm:"column:closing_techniques;comment:促单话术" json:"closing_techniques"` // 促单话术
CommunicationRhythm string `gorm:"column:communication_rhythm;comment:沟通节奏控制" json:"communication_rhythm"` // 沟通节奏控制
CreateAt time.Time `gorm:"column:create_at;default:CURRENT_TIMESTAMP" json:"create_at"`
}
// TableName AiAdviceTalk's table name
func (*AiAdviceTalk) TableName() string {
return TableNameAiAdviceTalk
}

135
internal/entitys/advicer.go Normal file
View File

@ -0,0 +1,135 @@
package entitys
type WordAnaReq struct {
WordFileUrl string `json:"word_file_url"`
}
// -------顾问
// DialectFeatures 方言特征
type DialectFeatures struct {
Region string `json:"region"` //方言使用程度
Intensity float64 `json:"intensity"` // 方言使用强度0-1
KeyWords []string `json:"KeyWords"`
}
func (e *DialectFeatures) Example() string {
return `{"region":"四川成都话","intensity":0.4,"key_words":["噻","要得","没得","不晓得","是不是"]}`
}
// SentencePatterns 句子模式
type SentencePatterns struct {
OpeningMode []string `json:"openingMode"` //开场模式
ExplanationMode []string `json:"explanationMode"` //解释模式
ConfirmationMode []string `json:"confirmationMode"` //确认模式
SummaryMode []string `json:"summaryMode"` //总结模式
TransitionMode []string `json:"transitionMode"` //过渡模式
}
func (e *SentencePatterns) Example() string {
return `{"openingMode":["我给你介绍一下","我们先来看一下"],"explanationMode":["是这样的","我跟你讲","你发现没得"],"confirmationMode":["对吧?","是不是嘛?","你晓得不?","明白了噻?"],"summaryMode":["所以说","简单说就是"],"transitionMode":["然后的话","再其次","还有一点"]}`
}
// PersonalityTags 个性标签
type PersonalityTags []string
func (e *PersonalityTags) Example() string {
return `["耐心细致","细节控"]`
}
// ToneTags 语气标签
type ToneTags struct {
Enthusiasm float64 `json:"enthusiasm"`
Patience float64 `json:"patience"`
Confidence float64 `json:"confidence"`
Friendliness float64 `json:"friendliness"`
Persuasion float64 `json:"persuasion"`
}
func (e *ToneTags) Example() string {
return `{"enthusiasm":0.8,"patience":0.9,"confidence":0.85,"friendliness":0.75,"persuasion":0.7}`
}
// SignatureDialogues 代表性对话示例
type SignatureDialogues struct {
Context string `json:"context"`
Dialogue string `json:"dialogue"` //解释
}
func (e *SignatureDialogues) Example() string {
return `[{"context":"客户质疑地块大小","dialogue":"哥14亩确实不大但你要在成都是2.5环内城买房这种是个普遍存在的一个现象。你看万景和绿城都是13亩中铁建只有8.8亩339那个帮泰只有11亩。我们虽然地小但楼间距开阔啊看过去都是200多米"},{"context":"客户担心物业费高","dialogue":"姐我懂你意思我们也觉得物业费是有点贵。但招商物业是铂金服务有管家送外卖、免费宠物喂养这些增值服务。你算一下就算贵一块钱十年也就多14000但好物业让房子增值不止这点"},{"context":"客户犹豫价格","dialogue":"说实话这个地段的地价都比28板块贵5000多但我们单价只贵3000。你看龙湖滨江云河颂套内单价都36000了我们才33000真的性价比高现在不买以后这个板块可能就买不起了。"}]`
}
// -------项目
// RegionValue 区域价值话术库
type RegionValue map[string]string
func (e *RegionValue) Example() string {
return `{"区位层级":["成华区2.5环内侧,这个位置真的稀缺","槐树店板块现在是成华区的number one板块","北接三板桥商圈,西靠万象城,东临火车东站","属于淮舜板块,万象城东的核心位置"],"地价论证":["我们地价19500华晨府20400棕榈也是2万+","2.5环内现在地价没有低于19000的","面粉贵了,面包不可能便宜"],"板块热度":["从21年新希望锦麟一品开始这边全是高端盘","龙湖最高端的滨江系列在这里,新希望的锦麟系列也在这里","各大品牌开发商争相恐后都在这边拿地"],"发展规划":["槐树店板块是棋盘成钢之后第二个富人区","整个板块都是300万到900万的总价段","未来全是改善型住宅,没有刚需盘"]}`
}
// CompetitionComparison 竞品对比话术
type CompetitionComparison map[string]string
func (e *CompetitionComparison) Example() string {
return `{"龙湖滨江云河颂":{"优点承认":"龙湖位置确实好,看沙河公园","价格对比":"他们单价32000-35000但得房率只有95%套内算下来36000+","优势突出":"我们得房率118平实得132平套内单价才33000"},"邦泰云锦":{"定位相似":"邦泰也是首个项目,要打造口碑","价格参考":"他们当时12800拿地现在卖34000","品质对比":"我们外立面全玻璃幕墙比他们成本高30%"}}`
}
// CoreSellingPoints 竞品对比话术
type CoreSellingPoints map[string]string
func (e *CoreSellingPoints) Example() string {
return `{"龙湖滨江云河颂":{"优点承认":"龙湖位置确实好,看沙河公园","价格对比":"他们单价32000-35000但得房率只有95%套内算下来36000+","优势突出":"我们得房率118平实得132平套内单价才33000"},"邦泰云锦":{"定位相似":"邦泰也是首个项目,要打造口碑","价格参考":"他们当时12800拿地现在卖34000","品质对比":"我们外立面全玻璃幕墙比他们成本高30%"}`
}
// SupportingFacilities 配套体系
type SupportingFacilities map[string]string
func (e *SupportingFacilities) Example() string {
return `{"交通配套":{"地铁":"双店路站350米7号线槐树店站550米4号线未来12号线","道路":"中环路、成洛大道到春熙路5个站","通达性":"到火车东站2个站到华西30分钟内"},"教育配套":{"幼儿园":"楼下公立幼儿园","小学":"城市附小锦汇东城(成华区生源最好的学校)","生源优势":"周边新盘都是300万+,生源纯粹"},"医疗配套":{"三甲医院":"市六医院、市二医院3公里内","顶尖医疗":"华西医院锦江院区30分钟车程","便利性":"到华西本部也是30分钟内"}}`
}
// DeveloperBacking 开发商背书
type DeveloperBacking map[string]string
func (e *DeveloperBacking) Example() string {
return `{"公司实力":"中信资产,多元化民营企业","资金安全":"在河南渑池有铝土矿每年稳定收入10亿","开发经验":"宜宾有5个项目贵州2个成都是首个项目","合作方":"招商铂金物业,首次与外部企业合作"}`
}
// -------销售话术
// NeedsMining 需求挖掘话术
type NeedsMining map[string]string
func (e *NeedsMining) Example() string {
return `{"预算需求":["你们总价想控制在多少以内?","是考虑按揭还是一次性?","月供能接受多少范围?"],"居住需求":["几个人住?有老人小孩吗?","主要是自住还是考虑投资?","现在住哪里?想改善哪些方面?"],"通勤需求":["在哪个位置上班?","主要开车还是坐地铁?","对地铁距离有要求吗?"]}`
}
// PainPointResponse 痛点应对策略
type PainPointResponse map[string]string
func (e *PainPointResponse) Example() string {
return `{"地块太小":{"承认事实":"14亩确实不大","普遍现象":"2.5环内都是小地块万景13亩中铁建8.8亩","转化优势":"但人少安静,楼间距反而更开阔","对比竞品":"339的邦泰才11亩人家上千万豪宅"},"物业费高":{"理解感受":"我懂你,我们也觉得有点贵","价值分析":"但6块里3块是增值服务保洁、送外卖","价格补贴":"前三年补贴到5块跟其他盘差不多"}}`
}
// ValueBuilding 价值塑造技巧
type ValueBuilding map[string]string
func (e *ValueBuilding) Example() string {
return `{"地段价值塑造":["买房最重要的是地段、地段、还是地段","核心地段的核心资产才保值增值","2.5环内的地卖一块少一块,不可再生"],"产品价值塑造":["我们是用改善的价格,买豪宅的标准","很多细节都是3000万豪宅才有的配置","外立面成本比竞品高30%,但单价差不多"]}`
}
// ClosingTechniques 促单话术
type ClosingTechniques map[string]string
func (e *ClosingTechniques) Example() string {
return `{"紧迫感营造":{"时间紧迫":["今天是月底最后一天,领导有压力价格可谈","我们刚刚开盘,还有额外优惠","月底冲业绩,价格最有弹性"],"房源稀缺":["118只剩20多套了好楼层不多","这栋楼就60户卖一套少一套","特价房只有这几套,今天不定可能就没了"]},"优惠策略":{"价格优惠":["今天定的话,我可以跟领导申请额外折扣","买车位的话,总价多给两个点优惠","一次性付款再优惠一个点"],"附加价值":["送一年物业费","送品牌家电礼包","优先选车位"]},"决策推动":{"小步推进":["要不先交个小定保留房源?","可以先排个号,有优惠优先通知你","今天不定的话,我帮你留意好楼层"]}}`
}
// CommunicationRhythm 沟通节奏控制
type CommunicationRhythm map[string]string
func (e *CommunicationRhythm) Example() string {
return `{"开场阶段":{"时间占比":"5%","目标":"建立关系,了解需求","关键动作":"亲切称呼,简单寒暄,确认看房重点"},"沙盘讲解":{"时间占比":"30%","目标":"建立价值认知","关键动作":"板块价值→周边配套→项目亮点→开发商介绍"}}`
}

View File

@ -0,0 +1,236 @@
package file_download
import (
"archive/zip"
"bytes"
"encoding/xml"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"path/filepath"
"strings"
"time"
)
// 下载文件
func DownloadFile(url string, validFunc func(resp *http.Response) error) ([]byte, string, error) {
// 设置超时
client := &http.Client{
Timeout: 30 * time.Second,
}
// 发送请求
resp, err := client.Get(url)
if err != nil {
return nil, "", err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, "", fmt.Errorf("HTTP %d: %s", resp.StatusCode, resp.Status)
}
if validFunc != nil {
err = validFunc(resp)
if err != nil {
return nil, "", err
}
}
// 读取文件数据
data, err := io.ReadAll(resp.Body)
if err != nil {
return nil, "", err
}
// 获取文件名
filename := getFilenameFromURL(url, resp)
return data, filename, nil
}
// 从 URL 或响应头获取文件名
func getFilenameFromURL(urlStr string, resp *http.Response) string {
// 1. 尝试从 Content-Disposition 头获取
contentDisposition := resp.Header.Get("Content-Disposition")
if contentDisposition != "" {
if strings.Contains(contentDisposition, "filename=") {
parts := strings.Split(contentDisposition, "filename=")
if len(parts) > 1 {
filename := strings.Trim(parts[1], `"' `)
return sanitizeFilename(filename)
}
}
}
// 2. 从 URL 路径获取
parsedURL, err := url.Parse(urlStr)
if err == nil {
path := parsedURL.Path
if path != "" {
filename := filepath.Base(path)
if filename != "" && filename != "." && filename != "/" {
return sanitizeFilename(filename)
}
}
}
// 3. 生成默认文件名
return fmt.Sprintf("word_%d.docx", time.Now().Unix())
}
// 清理文件名
func sanitizeFilename(filename string) string {
// 移除非法字符
illegalChars := []string{"/", "\\", ":", "*", "?", "\"", "<", ">", "|"}
for _, char := range illegalChars {
filename = strings.ReplaceAll(filename, char, "_")
}
// 确保有扩展名
if !strings.Contains(filename, ".") {
filename += ".docx"
}
return filename
}
// 从URL获取Word文件的纯文本内容
func GetWordTextFromURL(url string, validFunc func(resp *http.Response) error) (string, string, error) {
// 1. 下载文件
data, fileName, err := DownloadFile(url, validFunc)
if err != nil {
return "", "", fmt.Errorf("下载失败: %w", err)
}
// 2. 解析Word文件
text, err := parseWordContent(data)
if err != nil {
return "", "", fmt.Errorf("解析失败: %w", err)
}
return text, fileName, nil
}
// 解析Word内容 - 简单版本,只提取文字
func parseWordContent(data []byte) (string, error) {
reader := bytes.NewReader(data)
zipReader, err := zip.NewReader(reader, int64(len(data)))
if err != nil {
return "", fmt.Errorf("解压docx失败: %v", err)
}
var textBuilder strings.Builder
// 遍历 ZIP 文件中的文件
for _, file := range zipReader.File {
// 只处理文档主体文件
if file.Name == "word/document.xml" {
rc, err := file.Open()
if err != nil {
return "", fmt.Errorf("打开文档文件失败: %v", err)
}
defer rc.Close()
// 读取 XML 内容
xmlData, err := io.ReadAll(rc)
if err != nil {
return "", fmt.Errorf("读取XML失败: %v", err)
}
// 提取文本
text, err := parseWordXML(xmlData)
if err != nil {
return "", fmt.Errorf("解析XML失败: %v", err)
}
textBuilder.WriteString(text)
break // 找到主文档后退出循环
}
}
return textBuilder.String(), nil
}
// 解析 Word XML 文档
func parseWordXML(xmlData []byte) (string, error) {
type WordDocument struct {
XMLName xml.Name `xml:"document"`
Body struct {
Paragraphs []struct {
Runs []struct {
Text string `xml:"t"`
} `xml:"r"`
} `xml:"p"`
} `xml:"body"`
}
var doc WordDocument
if err := xml.Unmarshal(xmlData, &doc); err != nil {
// 尝试简化解析
return extractTextSimple(xmlData), nil
}
var textBuilder strings.Builder
for _, para := range doc.Body.Paragraphs {
for _, run := range para.Runs {
textBuilder.WriteString(run.Text)
}
textBuilder.WriteString("\n")
}
return textBuilder.String(), nil
}
// 简化文本提取(处理更复杂的文档结构)
func extractTextSimple(xmlData []byte) string {
var textBuilder strings.Builder
// 简单提取 <w:t> 标签内容
decoder := xml.NewDecoder(bytes.NewReader(xmlData))
for {
token, err := decoder.Token()
if err == io.EOF {
break
}
if err != nil {
continue
}
if startElem, ok := token.(xml.StartElement); ok {
if startElem.Name.Local == "t" {
// 读取文本内容
if nextToken, err := decoder.Token(); err == nil {
if charData, ok := nextToken.(xml.CharData); ok {
textBuilder.WriteString(string(charData))
}
}
}
}
}
return textBuilder.String()
}
// 判断是否为 Word 文件
func IsWordFile(resp *http.Response) error {
contentType := resp.Header.Get("Content-Type")
wordContentTypes := []string{
"application/msword",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"application/vnd.ms-word",
"application/octet-stream", // 有些服务器可能返回这个
}
contentType = strings.ToLower(contentType)
for _, ct := range wordContentTypes {
if strings.Contains(contentType, ct) {
return nil
}
}
return errors.New("错误的文件类型")
}

View File

@ -26,7 +26,7 @@ type RouterServer struct {
// SetupRoutes 设置路由
func SetupRoutes(app *fiber.App, ChatService *services.ChatService, sessionService *services.SessionService, task *services.TaskService,
gateway *gateway.Gateway, callbackService *services.CallbackService, chatHist *services.HistoryService,
capabilityService *services.CapabilityService,
capabilityService *services.CapabilityService, advice *services.AdviceService,
) {
app.Use(func(c *fiber.Ctx) error {
// 设置 CORS 头
@ -98,6 +98,9 @@ func SetupRoutes(app *fiber.App, ChatService *services.ChatService, sessionServi
// 能力
r.Post("/capability/product/ingest", capabilityService.ProductIngest) // 商品数据提取
r.Post("/capability/product/ingest/:thread_id/confirm", capabilityService.ProductIngestConfirm) // 商品数据提取确认
advicer := r.Group("advice/")
advicer.Post("file/word", advice.WordAna)
}
func routerSocket(app *fiber.App, chatService *services.ChatService) {

View File

@ -0,0 +1,62 @@
package services
import (
"ai_scheduler/internal/biz"
"ai_scheduler/internal/config"
"ai_scheduler/internal/entitys"
"ai_scheduler/internal/pkg/file_download"
"context"
"errors"
"net/url"
"github.com/gofiber/fiber/v2"
)
// ChatHandler 聊天处理器
type AdviceService struct {
adviceBiz *biz.AdviceBiz
cfg *config.Config
}
// NewChatHandler 创建聊天处理器
func NewAdviceService(
adviceBiz *biz.AdviceBiz,
cfg *config.Config,
) *AdviceService {
return &AdviceService{
adviceBiz: adviceBiz,
cfg: cfg,
}
}
func (a *AdviceService) WordAna(c *fiber.Ctx) error {
req := &entitys.WordAnaReq{}
if err := c.BodyParser(req); err != nil {
return err
}
// URL 解码
fileURL, err := url.PathUnescape(req.WordFileUrl)
if err != nil {
return errors.New("URL 解码失败")
}
result, _, err := file_download.GetWordTextFromURL(fileURL, file_download.IsWordFile)
if err != nil {
return err
}
return a.adviceBiz.WordAna(c.UserContext(), result)
}
func (a *AdviceService) WordAnat(path string) error {
// URL 解码
fileURL, err := url.PathUnescape(path)
if err != nil {
return errors.New("URL 解码失败")
}
result, _, err := file_download.GetWordTextFromURL(fileURL, file_download.IsWordFile)
if err != nil {
return err
}
return a.adviceBiz.WordAna(context.Background(), result)
}

View File

@ -0,0 +1,15 @@
package services
import (
"testing"
)
func Test_WordAna(t *testing.T) {
Run()
advicer.WordAnat("https://attachment-public.oss-cn-hangzhou.aliyuncs.com/ai-scheduler/data-analytics/word/content.docx")
}
var (
advicer *AdviceService
)

View File

@ -6,6 +6,7 @@ import (
dingtalk2 "ai_scheduler/internal/biz/handle/dingtalk"
"ai_scheduler/internal/biz/handle/qywx"
"ai_scheduler/internal/biz/llm_service"
"ai_scheduler/internal/biz/llm_service/third_party"
"ai_scheduler/internal/biz/tools_regis"
"ai_scheduler/internal/config"
"ai_scheduler/internal/data/impl"
@ -29,13 +30,13 @@ import (
)
func Test_Report(t *testing.T) {
run()
Run()
a := cronService.CronReportSendDingTalk(context.Background())
t.Log(a)
}
func Test_Report_QYWX(t *testing.T) {
run()
Run()
a := cronService.CronReportSendQywx(context.Background())
t.Log(a)
}
@ -48,7 +49,7 @@ var (
)
// run 函数是程序的入口函数,负责初始化和配置各个组件
func run() {
func Run() {
// 加载测试配置
// configConfig, err = config.LoadConfigWithTest()
configConfig, err = config.LoadConfigWithEnv()
@ -62,6 +63,7 @@ func run() {
botConfigImpl := impl.NewBotConfigImpl(db)
botGroupImpl := impl.NewBotGroupImpl(db)
botUserImpl := impl.NewBotUserImpl(db)
reportDailyCacheImpl := impl.NewReportDailyCacheImpl(db)
// 初始化Do业务对象
doDo := do.NewDo(sessionImpl, sysImpl, taskImpl, chatHisImpl, configConfig)
// 初始化Ollama客户端
@ -92,7 +94,7 @@ func run() {
// 初始化Ollama服务
ollamaService := llm_service.NewOllamaGenerate(client, utils_vllmClient, configConfig, chatHisImpl)
// 初始化工具管理器
manager := tools.NewManager(configConfig, client)
manager := tools.NewManager(configConfig, client, rdb)
// 初始化钉钉联系人客户端
contactClient, _ := dingtalk.NewContactClient(configConfig)
// 初始化钉钉记事本客户端
@ -117,11 +119,15 @@ func run() {
botGroupConfigImpl := impl.NewBotGroupConfigImpl(db)
botGroupQywxImpl := impl.NewBotGroupQywxImpl(db)
qywxAuth := qywx.NewAuth(configConfig, rdb)
macro := do.NewMacro(botGroupImpl, reportDailyCacheImpl, configConfig, rdb)
group := qywx.NewGroup(botGroupQywxImpl, qywxAuth)
other := qywx.NewOther(qywxAuth)
qywxAppBiz := biz.NewQywxAppBiz(configConfig, botGroupQywxImpl, group, other)
groupConfigBiz := biz.NewGroupConfigBiz(toolRegis, utils_ossClient, botGroupConfigImpl, registry, configConfig, impl.NewReportDailyCacheImpl(db), rdb)
dingTalkBotBiz := biz.NewDingTalkBotBiz(doDo, handle, botConfigImpl, botGroupImpl, user, botChatHisImpl, impl.NewReportDailyCacheImpl(db), manager, configConfig, sendCardClient, groupConfigBiz)
groupConfigBiz := biz.NewGroupConfigBiz(toolRegis, utils_ossClient, botGroupConfigImpl, registry, configConfig, reportDailyCacheImpl, rdb, macro, manager, handle)
dingTalkBotBiz := biz.NewDingTalkBotBiz(doDo, handle, botConfigImpl, botGroupImpl, user, botChatHisImpl, reportDailyCacheImpl, manager, configConfig, sendCardClient, groupConfigBiz, macro)
// 初始化钉钉机器人服务
cronService = NewCronService(configConfig, dingTalkBotBiz, qywxAppBiz, groupConfigBiz)
hsyq := third_party.NewHsyq()
advicerbiz := biz.NewAdviceBiz(hsyq)
advicer = NewAdviceService(advicerbiz, configConfig)
}

View File

@ -15,4 +15,5 @@ var ProviderSetServices = wire.NewSet(
NewHistoryService,
NewCapabilityService,
NewCronService,
NewAdviceService,
)

108
internal/test/bench_test.go Normal file
View File

@ -0,0 +1,108 @@
package test
import (
"runtime"
"testing"
)
// 测试用的结构体
type Person struct {
Name string
Age int
Address string
Email string
Phone string
Balance float64
Active bool
}
// 返回指针
func NewPersonPtr(name string, age int) *Person {
return &Person{
Name: name,
Age: age,
Address: "Some Address",
Email: "test@example.com",
Phone: "1234567890",
Balance: 1000.0,
Active: true,
}
}
// 返回值
func NewPersonValue(name string, age int) Person {
return Person{
Name: name,
Age: age,
Address: "Some Address",
Email: "test@example.com",
Phone: "1234567890",
Balance: 1000.0,
Active: true,
}
}
var globalPtr *Person
var globalValue Person
func BenchmarkSmallStruct(b *testing.B) {
runtime.GC()
b.Run("ValueWithSmallStruct", func(b *testing.B) {
for i := 0; i < b.N; i++ {
p := NewPersonValue("John", 30)
globalValue = p
}
})
runtime.Gosched()
runtime.GC()
b.Run("PointerWithSmallStruct", func(b *testing.B) {
for i := 0; i < b.N; i++ {
p := NewPersonPtr("John", 30)
globalPtr = p
}
})
}
var globalLargePtr *LargeStruct
var globalLargeValue LargeStruct
func BenchmarkLargeStruct(b *testing.B) {
runtime.GC()
b.Run("ValueWithLargeStruct", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
s := NewLargeValue(1)
globalLargeValue = s
}
})
runtime.Gosched()
runtime.GC()
b.Run("PointerWithLargeStruct", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
p := NewLargePtr(1)
globalLargePtr = p
}
})
}
func NewLargePtr(id int) *LargeStruct {
return &LargeStruct{
ID: id,
}
}
// 返回值
func NewLargeValue(id int) LargeStruct {
return LargeStruct{
ID: id,
}
}
// Benchmark 大结构体指针
type LargeStruct struct {
Data [1024]byte
ID int
}