1
This commit is contained in:
@@ -3,30 +3,19 @@ module video-factory
|
||||
go 1.26.1
|
||||
|
||||
require (
|
||||
github.com/cloudwego/eino v0.9.9
|
||||
github.com/cloudwego/eino-ext/components/model/qwen v0.1.9
|
||||
github.com/cloudwego/eino-ext/libs/acl/openai v0.1.17
|
||||
github.com/gogf/gf/contrib/drivers/sqlite/v2 v2.10.2
|
||||
github.com/gogf/gf/v2 v2.10.2
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v1.6.0 // indirect
|
||||
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
||||
github.com/buger/jsonparser v1.2.0 // indirect
|
||||
github.com/bytedance/gopkg v0.1.4 // indirect
|
||||
github.com/bytedance/sonic v1.15.2 // indirect
|
||||
github.com/bytedance/sonic/loader v0.5.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/clbanning/mxj/v2 v2.7.0 // indirect
|
||||
github.com/clipperhouse/displaywidth v0.11.0 // indirect
|
||||
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.7 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/eino-contrib/jsonschema v1.0.3 // indirect
|
||||
github.com/emirpasic/gods/v2 v2.0.0-alpha // indirect
|
||||
github.com/evanphx/json-patch v0.5.2 // indirect
|
||||
github.com/fatih/color v1.19.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.10.1 // indirect
|
||||
github.com/glebarez/go-sqlite v1.21.2 // indirect
|
||||
@@ -35,42 +24,23 @@ require (
|
||||
github.com/goccy/go-json v0.10.6 // indirect
|
||||
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/goph/emperror v0.17.2 // indirect
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
|
||||
github.com/grokify/html-strip-tags-go v0.1.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/magiconair/properties v1.8.10 // indirect
|
||||
github.com/mailru/easyjson v0.9.2 // indirect
|
||||
github.com/mattn/go-colorable v0.1.15 // indirect
|
||||
github.com/mattn/go-isatty v0.0.22 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.24 // indirect
|
||||
github.com/meguminnnnnnnnn/go-openai v0.1.5 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
|
||||
github.com/nikolalohinski/gonja v1.5.3 // indirect
|
||||
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 // indirect
|
||||
github.com/olekukonko/errors v1.3.0 // indirect
|
||||
github.com/olekukonko/ll v0.1.8 // indirect
|
||||
github.com/olekukonko/tablewriter v1.1.4 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.4.1 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/sirupsen/logrus v1.9.4 // indirect
|
||||
github.com/slongfield/pyfmt v0.0.0-20220222012616-ea85ff4c361f // indirect
|
||||
github.com/smarty/assertions v1.16.0 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
|
||||
github.com/yargevad/filepathx v1.0.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/otel v1.44.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.44.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.44.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.44.0 // indirect
|
||||
go.uber.org/mock v0.5.0 // indirect
|
||||
golang.org/x/arch v0.28.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20260611194520-c48552f49976 // indirect
|
||||
golang.org/x/net v0.56.0 // indirect
|
||||
golang.org/x/sys v0.46.0 // indirect
|
||||
golang.org/x/text v0.38.0 // indirect
|
||||
|
||||
@@ -1,23 +1,5 @@
|
||||
github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=
|
||||
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
github.com/airbrake/gobrake v3.6.1+incompatible/go.mod h1:wM4gu3Cn0W0K7GUuVWnlXZU11AGBXMILnrdOU8Kn00o=
|
||||
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=
|
||||
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
|
||||
github.com/buger/jsonparser v1.2.0 h1:4EFcvK1kD4jyj6YqNK6skK6w+y7FHHBR+XBCtxwu/6g=
|
||||
github.com/buger/jsonparser v1.2.0/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
|
||||
github.com/bugsnag/bugsnag-go v1.4.0/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8=
|
||||
github.com/bugsnag/panicwrap v1.2.0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE=
|
||||
github.com/bytedance/gopkg v0.1.4 h1:oZnQwnX82KAIWb7033bEwtxvTqXcYMxDBaQxo5JJHWM=
|
||||
github.com/bytedance/gopkg v0.1.4/go.mod h1:v1zWfPm21Fb+OsyXN2VAHdL6TBb2L88anLQgdyje6R4=
|
||||
github.com/bytedance/mockey v1.3.0 h1:ONLRdvhqmCfr9rTasUB8ZKCfvbdD2tohOg4u+4Q/ed0=
|
||||
github.com/bytedance/mockey v1.3.0/go.mod h1:1BPHF9sol5R1ud/+0VEHGQq/+i2lN+GTsr3O2Q9IENY=
|
||||
github.com/bytedance/sonic v1.15.2 h1:90H+rcF/FwLXwfB1cudOLq/je83n683Utf4Cbp0xHCo=
|
||||
github.com/bytedance/sonic v1.15.2/go.mod h1:mT2NbXunuaEbnZ+mRIX/vYqKISmgEuHFDI4UzmKx2SA=
|
||||
github.com/bytedance/sonic/loader v0.5.1 h1:Ygpfa9zwRCCKSlrp5bBP/b/Xzc3VxsAW+5NIYXrOOpI=
|
||||
github.com/bytedance/sonic/loader v0.5.1/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
|
||||
github.com/certifi/gocertifi v0.0.0-20190105021004-abcd57078448/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME=
|
||||
@@ -26,36 +8,18 @@ github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSE
|
||||
github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0=
|
||||
github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
|
||||
github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
|
||||
github.com/cloudwego/base64x v0.1.7 h1:NppS+Fgzg5ovhn4NkUXaDT3x9jldgH5ToMCqzBSi2zI=
|
||||
github.com/cloudwego/base64x v0.1.7/go.mod h1:Cu1PV9zfrSf7ET2tIbWbbEy7jO7HHJ13q4X2SQ8aWYg=
|
||||
github.com/cloudwego/eino v0.9.9 h1:x63hvRif6ANPh9YEPoTIrp1potEeoLQFAjOclKaX/Kg=
|
||||
github.com/cloudwego/eino v0.9.9/go.mod h1:OBD1mrkfkt/pJa4rkg1P0VnaMeOVl7l8IAdEqY//3IQ=
|
||||
github.com/cloudwego/eino-ext/components/model/qwen v0.1.9 h1:xCz/mp43JeWqupjPR3zLRArmwC6P29/6lTwbwh1yzYM=
|
||||
github.com/cloudwego/eino-ext/components/model/qwen v0.1.9/go.mod h1:slTGTuhzkzhNavf+1UtUg1FvUSA31iNAF+rq1mT4SnI=
|
||||
github.com/cloudwego/eino-ext/libs/acl/openai v0.1.17 h1:EeVcR1TslRA2IdNW1h/2LaGbPlffwGhQm99jM3zWZiI=
|
||||
github.com/cloudwego/eino-ext/libs/acl/openai v0.1.17/go.mod h1:Zkcx6DPTR2NfWmtSXbhItswGw6hqUezNPhNcke0pOG8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/eino-contrib/jsonschema v1.0.3 h1:2Kfsm1xlMV0ssY2nuxshS4AwbLFuqmPmzIjLVJ1Fsp0=
|
||||
github.com/eino-contrib/jsonschema v1.0.3/go.mod h1:cpnX4SyKjWjGC7iN2EbhxaTdLqGjCi0e9DxpLYxddD4=
|
||||
github.com/emirpasic/gods/v2 v2.0.0-alpha h1:dwFlh8pBg1VMOXWGipNMRt8v96dKAIvBehtCt6OtunU=
|
||||
github.com/emirpasic/gods/v2 v2.0.0-alpha/go.mod h1:W0y4M2dtBB9U5z3YlghmpuUhiaZT2h6yoeE+C1sCp6A=
|
||||
github.com/evanphx/json-patch v0.5.2 h1:xVCHIVMUu1wtM/VkR9jVZ45N3FhZfYMMYGorLCR8P3k=
|
||||
github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ=
|
||||
github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
|
||||
github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho=
|
||||
github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo=
|
||||
github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
|
||||
github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo=
|
||||
github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
|
||||
github.com/go-check/check v0.0.0-20180628173108-788fd7840127 h1:0gkP6mzaMqkmpcJYCFOLkIBwI7xFExG03bbkOkCvUPI=
|
||||
github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
@@ -63,66 +27,32 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU=
|
||||
github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gogf/gf/contrib/drivers/sqlite/v2 v2.10.2 h1:KLS68SWS2W749x7e+eCCOO3UD2Sbw+bIbLEPR8o1FXw=
|
||||
github.com/gogf/gf/contrib/drivers/sqlite/v2 v2.10.2/go.mod h1:uLcsu73PfpyhRc0Jq0gGAWQjN1tyGU9iBRrYgt/lu7g=
|
||||
github.com/gogf/gf/v2 v2.10.2 h1:46IO0Uc8e85/FqdftJFskfDejJLBL0JBnGS5qOftUu8=
|
||||
github.com/gogf/gf/v2 v2.10.2/go.mod h1:Svl1N+E8G/QshU2DUbh/3J/AJauqCgUnxHurXWR4Qx0=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
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=
|
||||
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=
|
||||
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||
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/goph/emperror v0.17.2 h1:yLapQcmEsO0ipe9p5TaN22djm3OFV/TfM/fcYP0/J18=
|
||||
github.com/goph/emperror v0.17.2/go.mod h1:+ZbQ+fUNO/6FNiUo0ujtMjhgad9Xa6fQL9KhH4LNHic=
|
||||
github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
|
||||
github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
|
||||
github.com/grokify/html-strip-tags-go v0.1.0 h1:03UrQLjAny8xci+R+qjCce/MYnpNXCtgzltlQbOBae4=
|
||||
github.com/grokify/html-strip-tags-go v0.1.0/go.mod h1:ZdzgfHEzAfz9X6Xe5eBLVblWIxXfYSQ40S/VKrAOGpc=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
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=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
|
||||
github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/mailru/easyjson v0.9.2 h1:dX8U45hQsZpxd80nLvDGihsQ/OxlvTkVUXH2r/8cb2M=
|
||||
github.com/mailru/easyjson v0.9.2/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
|
||||
github.com/mattn/go-colorable v0.1.15 h1:+u9SLTRGnXv73cEsnsmoZBom+dMU88B2M0aDcWy0/jY=
|
||||
github.com/mattn/go-colorable v0.1.15/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4=
|
||||
github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
|
||||
github.com/mattn/go-runewidth v0.0.24 h1:cpokDiIn0MGnhdHwuWnJBITySJ20QyNGnY2kR/ay2DU=
|
||||
github.com/mattn/go-runewidth v0.0.24/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
||||
github.com/meguminnnnnnnnn/go-openai v0.1.5 h1:K9XFfnEUj9E+9djustmfa4eIdg8Q2vWD4mGv+AHbQ2k=
|
||||
github.com/meguminnnnnnnnn/go-openai v0.1.5/go.mod h1:qs96ysDmxhE4BZoU45I43zcyfnaYxU3X+aRzLko/htY=
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
|
||||
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/nikolalohinski/gonja v1.5.3 h1:GsA+EEaZDZPGJ8JtpeGN78jidhOlxeJROpqMT9fTj9c=
|
||||
github.com/nikolalohinski/gonja v1.5.3/go.mod h1:RmjwxNiXAEqcq1HeK5SSMmqFJvKOfTfXhkJv6YBtPa4=
|
||||
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 h1:zrbMGy9YXpIeTnGj4EljqMiZsIcE09mmF8XsD5AYOJc=
|
||||
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6/go.mod h1:rEKTHC9roVVicUIfZK7DYrdIoM0EOr8mK1Hj5s3JjH0=
|
||||
github.com/olekukonko/errors v1.3.0 h1:teJvgLGUEqMzBUms+Dj3/3szNqCG/Jdw9iDbum8fR6U=
|
||||
@@ -131,15 +61,6 @@ github.com/olekukonko/ll v0.1.8 h1:ysHCJRGHYKzmBSdz9w5AySztx7lG8SQY+naTGYUbsz8=
|
||||
github.com/olekukonko/ll v0.1.8/go.mod h1:RPRC6UcscfFZgjo1nulkfMH5IM0QAYim0LfnMvUuozw=
|
||||
github.com/olekukonko/tablewriter v1.1.4 h1:ORUMI3dXbMnRlRggJX3+q7OzQFDdvgbN9nVWj1drm6I=
|
||||
github.com/olekukonko/tablewriter v1.1.4/go.mod h1:+kedxuyTtgoZLwif3P1Em4hARJs+mVnzKxmsCL/C5RY=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/pelletier/go-toml/v2 v2.4.1 h1:j5OMOImsH+j2k7GJ5YO+RxfWwohNiH6t5zB/+h3bagc=
|
||||
github.com/pelletier/go-toml/v2 v2.4.1/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
@@ -147,37 +68,8 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/rollbar/rollbar-go v1.0.2/go.mod h1:AcFs5f0I+c71bpHlXNNDbOWJiKwjFDtISeXco0L5PKQ=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
|
||||
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
|
||||
github.com/slongfield/pyfmt v0.0.0-20220222012616-ea85ff4c361f h1:Z2cODYsUxQPofhpYRMQVwWz4yUVpHF+vPi+eUdruUYI=
|
||||
github.com/slongfield/pyfmt v0.0.0-20220222012616-ea85ff4c361f/go.mod h1:JqzWyvTuI2X4+9wOHmKSQCYxybB/8j6Ko43qVmXDuZg=
|
||||
github.com/smarty/assertions v1.16.0 h1:EvHNkdRA4QHMrn75NZSoUQ/mAUXAYWfatfB01yTCzfY=
|
||||
github.com/smarty/assertions v1.16.0/go.mod h1:duaaFdCS0K9dnoM50iyek/eYINOZ64gbh1Xlf6LG7AI=
|
||||
github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY=
|
||||
github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
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/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=
|
||||
github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE=
|
||||
github.com/yargevad/filepathx v1.0.0 h1:SYcT+N3tYGi+NvazubCNlvgIPbzAk7i7y2dwg3I5FYc=
|
||||
github.com/yargevad/filepathx v1.0.0/go.mod h1:BprfX/gpYNJHJfc35GjRRpVcwWXS89gGulUIU5tK3tA=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU=
|
||||
@@ -192,35 +84,15 @@ go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/
|
||||
go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
|
||||
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
||||
golang.org/x/arch v0.28.0 h1:wVwVdqsTuUbJvhYVCspQYwZXHNYeLSoZnmHD+ggddpQ=
|
||||
golang.org/x/arch v0.28.0/go.mod h1:0X+GdSIP+kL5wPmpK7sdkEVTt2XoYP0cSjQSbZBwOi8=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
|
||||
golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
|
||||
golang.org/x/exp v0.0.0-20260611194520-c48552f49976 h1:X8Hz2ImujgbmetVuW+w2YkyZChE3cBpZi2P158rTG9M=
|
||||
golang.org/x/exp v0.0.0-20260611194520-c48552f49976/go.mod h1:vnf4pv9iKZXY58sQE1L86zmNWJ4159e1RkcWiLCkeEY=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
|
||||
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
|
||||
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc=
|
||||
golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
|
||||
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/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=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE=
|
||||
|
||||
Binary file not shown.
+146
-61
@@ -1,17 +1,17 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/cloudwego/eino/components/model"
|
||||
"video-factory/shortdrama/model/adapter"
|
||||
)
|
||||
|
||||
// ModelConfig 模型配置 — 所有字段必须显式提供,无硬编码默认值
|
||||
// ModelConfig 模型配置
|
||||
type ModelConfig struct {
|
||||
Provider string // 模型供应商(qwen/openai_compatible),空则自动检测
|
||||
ModelName string // 对话模型名
|
||||
APIKey string // API密钥
|
||||
BaseURL string // API地址
|
||||
@@ -20,53 +20,8 @@ type ModelConfig struct {
|
||||
Timeout time.Duration // HTTP请求超时(0表示默认)
|
||||
}
|
||||
|
||||
// context keys
|
||||
type ctxKey string
|
||||
|
||||
const (
|
||||
ctxKeyAPIKey ctxKey = "api_key"
|
||||
ctxKeyBaseURL ctxKey = "base_url"
|
||||
ctxKeyDramaId ctxKey = "drama_id"
|
||||
)
|
||||
|
||||
// WithDramaId 将短剧ID注入 context,供工具函数读取场景图片
|
||||
func WithDramaId(ctx context.Context, dramaId int64) context.Context {
|
||||
return context.WithValue(ctx, ctxKeyDramaId, dramaId)
|
||||
}
|
||||
|
||||
// GetDramaId 从 context 获取短剧ID
|
||||
func GetDramaId(ctx context.Context) int64 {
|
||||
if v, ok := ctx.Value(ctxKeyDramaId).(int64); ok {
|
||||
return v
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// WithModelConfig 将模型配置注入 context,供工具函数读取
|
||||
func WithModelConfig(ctx context.Context, cfg *ModelConfig) context.Context {
|
||||
ctx = context.WithValue(ctx, ctxKeyAPIKey, cfg.APIKey)
|
||||
ctx = context.WithValue(ctx, ctxKeyBaseURL, cfg.BaseURL)
|
||||
return ctx
|
||||
}
|
||||
|
||||
// GetAPIKey 从 context 获取 API key
|
||||
func GetAPIKey(ctx context.Context) string {
|
||||
if v, ok := ctx.Value(ctxKeyAPIKey).(string); ok && v != "" {
|
||||
return v
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetBaseURL 从 context 获取 API 地址
|
||||
func GetBaseURL(ctx context.Context) string {
|
||||
if v, ok := ctx.Value(ctxKeyBaseURL).(string); ok && v != "" {
|
||||
return v
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// NewChatModel 根据配置初始化聊天模型(使用适配器模式,自动选择供应商)
|
||||
func NewChatModel(ctx context.Context, cfg *ModelConfig) (cm model.ChatModel, err error) {
|
||||
// CallChatModel 调用大模型聊天接口(OpenAI 兼容格式)
|
||||
func CallChatModel(ctx context.Context, cfg *ModelConfig, req *ChatRequest) (*ChatResponse, error) {
|
||||
if cfg == nil {
|
||||
return nil, fmt.Errorf("模型配置不能为空")
|
||||
}
|
||||
@@ -80,14 +35,144 @@ func NewChatModel(ctx context.Context, cfg *ModelConfig) (cm model.ChatModel, er
|
||||
return nil, fmt.Errorf("API 地址未配置")
|
||||
}
|
||||
|
||||
adapterCfg := &adapter.ChatModelConfig{
|
||||
Provider: adapter.ChatProvider(cfg.Provider),
|
||||
ModelName: cfg.ModelName,
|
||||
APIKey: cfg.APIKey,
|
||||
BaseURL: cfg.BaseURL,
|
||||
MaxTokens: cfg.MaxTokens,
|
||||
Temperature: cfg.Temperature,
|
||||
Timeout: cfg.Timeout,
|
||||
timeout := cfg.Timeout
|
||||
if timeout <= 0 {
|
||||
timeout = 60 * time.Second
|
||||
}
|
||||
return adapter.NewChatModel(ctx, adapterCfg)
|
||||
|
||||
body, err := buildReqBody(cfg.ModelName, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
url := trimSlashes(cfg.BaseURL) + "/v1/chat/completions"
|
||||
httpReq, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(body))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("创建请求失败: %w", err)
|
||||
}
|
||||
httpReq.Header.Set("Authorization", "Bearer "+cfg.APIKey)
|
||||
httpReq.Header.Set("Content-Type", "application/json")
|
||||
|
||||
client := &http.Client{Timeout: timeout}
|
||||
resp, err := client.Do(httpReq)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("请求失败: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("读取响应失败: %w", err)
|
||||
}
|
||||
|
||||
return parseRespBody(respBody)
|
||||
}
|
||||
|
||||
// ==================== 内部实现 ====================
|
||||
|
||||
type openAIReqBody struct {
|
||||
Model string `json:"model"`
|
||||
Messages []*ChatMessage `json:"messages"`
|
||||
MaxTokens int `json:"max_tokens,omitempty"`
|
||||
Temperature float32 `json:"temperature,omitempty"`
|
||||
Stream bool `json:"stream,omitempty"`
|
||||
Tools []openAIToolDef `json:"tools,omitempty"`
|
||||
}
|
||||
|
||||
type openAIToolDef struct {
|
||||
Type string `json:"type"`
|
||||
Function openAIToolFunction `json:"function"`
|
||||
}
|
||||
|
||||
type openAIToolFunction struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Parameters map[string]any `json:"parameters"`
|
||||
}
|
||||
|
||||
type openAIRespBody struct {
|
||||
Choices []openAIChoice `json:"choices"`
|
||||
Error *struct {
|
||||
Message string `json:"message"`
|
||||
Code string `json:"code"`
|
||||
} `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
type openAIChoice struct {
|
||||
Index int `json:"index"`
|
||||
Message openAIMsg `json:"message"`
|
||||
}
|
||||
|
||||
type openAIMsg struct {
|
||||
Content string `json:"content"`
|
||||
ToolCalls []openAIToolCall `json:"tool_calls,omitempty"`
|
||||
}
|
||||
|
||||
type openAIToolCall struct {
|
||||
ID string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Function openAIFuncCall `json:"function"`
|
||||
}
|
||||
|
||||
type openAIFuncCall struct {
|
||||
Name string `json:"name"`
|
||||
Arguments string `json:"arguments"`
|
||||
}
|
||||
|
||||
func buildReqBody(model string, req *ChatRequest) ([]byte, error) {
|
||||
body := openAIReqBody{
|
||||
Model: model,
|
||||
Messages: req.Messages,
|
||||
MaxTokens: req.MaxTokens,
|
||||
Temperature: req.Temperature,
|
||||
Stream: req.Stream,
|
||||
}
|
||||
if len(req.Tools) > 0 {
|
||||
body.Tools = make([]openAIToolDef, 0, len(req.Tools))
|
||||
for _, t := range req.Tools {
|
||||
body.Tools = append(body.Tools, openAIToolDef{
|
||||
Type: "function",
|
||||
Function: openAIToolFunction{
|
||||
Name: t.Name,
|
||||
Description: t.Description,
|
||||
Parameters: t.Parameters,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
return json.Marshal(body)
|
||||
}
|
||||
|
||||
func parseRespBody(data []byte) (*ChatResponse, error) {
|
||||
var resp openAIRespBody
|
||||
if err := json.Unmarshal(data, &resp); err != nil {
|
||||
return nil, fmt.Errorf("解析响应失败: %s", string(data))
|
||||
}
|
||||
if resp.Error != nil {
|
||||
return nil, fmt.Errorf("API错误(code=%s): %s", resp.Error.Code, resp.Error.Message)
|
||||
}
|
||||
if len(resp.Choices) == 0 {
|
||||
return nil, fmt.Errorf("响应为空")
|
||||
}
|
||||
|
||||
msg := resp.Choices[0].Message
|
||||
cr := &ChatResponse{Content: msg.Content}
|
||||
if len(msg.ToolCalls) > 0 {
|
||||
cr.ToolCalls = make([]*ToolCall, 0, len(msg.ToolCalls))
|
||||
for _, tc := range msg.ToolCalls {
|
||||
cr.ToolCalls = append(cr.ToolCalls, &ToolCall{
|
||||
ID: tc.ID,
|
||||
Name: tc.Function.Name,
|
||||
Arguments: tc.Function.Arguments,
|
||||
})
|
||||
}
|
||||
}
|
||||
return cr, nil
|
||||
}
|
||||
|
||||
func trimSlashes(s string) string {
|
||||
for len(s) > 0 && s[len(s)-1] == '/' {
|
||||
s = s[:len(s)-1]
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package agent
|
||||
|
||||
import "context"
|
||||
|
||||
type contextKey string
|
||||
|
||||
const ctxKeyDramaID contextKey = "drama_id"
|
||||
|
||||
// WithDramaID 将短剧ID存入context
|
||||
func WithDramaID(ctx context.Context, dramaID int64) context.Context {
|
||||
return context.WithValue(ctx, ctxKeyDramaID, dramaID)
|
||||
}
|
||||
|
||||
// GetDramaID 从context中获取短剧ID
|
||||
func GetDramaID(ctx context.Context) int64 {
|
||||
if id, ok := ctx.Value(ctxKeyDramaID).(int64); ok {
|
||||
return id
|
||||
}
|
||||
return 0
|
||||
}
|
||||
@@ -6,23 +6,21 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/cloudwego/eino/components/model"
|
||||
"github.com/cloudwego/eino/schema"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
// ReActAgent 实现 ReAct 模式的智能体
|
||||
type ReActAgent struct {
|
||||
model model.ChatModel
|
||||
cfg *ModelConfig
|
||||
tools []*ToolInfo
|
||||
systemPrompt string
|
||||
maxStep int
|
||||
}
|
||||
|
||||
// NewReActAgent 创建 ReAct 智能体
|
||||
func NewReActAgent(ctx context.Context, chatModel model.ChatModel, tools []*ToolInfo, systemPrompt string, maxStep int) *ReActAgent {
|
||||
func NewReActAgent(cfg *ModelConfig, tools []*ToolInfo, systemPrompt string, maxStep int) *ReActAgent {
|
||||
return &ReActAgent{
|
||||
model: chatModel,
|
||||
cfg: cfg,
|
||||
tools: tools,
|
||||
systemPrompt: systemPrompt,
|
||||
maxStep: maxStep,
|
||||
@@ -32,15 +30,9 @@ func NewReActAgent(ctx context.Context, chatModel model.ChatModel, tools []*Tool
|
||||
// Run 执行 ReAct 循环
|
||||
// 标准流程: 思考 → 行动(调用工具) → 观察(工具结果) → 重复 → 最终回答
|
||||
func (a *ReActAgent) Run(ctx context.Context, userInput string) (string, error) {
|
||||
messages := []*schema.Message{
|
||||
schema.SystemMessage(a.systemPrompt),
|
||||
schema.UserMessage(userInput),
|
||||
}
|
||||
|
||||
// 构建 toolInfos 传给模型
|
||||
toolInfos := make([]*schema.ToolInfo, 0, len(a.tools))
|
||||
for _, t := range a.tools {
|
||||
toolInfos = append(toolInfos, t.ToEinoToolInfo())
|
||||
messages := []*ChatMessage{
|
||||
{Role: RoleSystem, Content: a.systemPrompt},
|
||||
{Role: RoleUser, Content: userInput},
|
||||
}
|
||||
|
||||
g.Log().Infof(ctx, "ReAct 开始执行,maxStep=%d, tools=%d", a.maxStep, len(a.tools))
|
||||
@@ -48,9 +40,13 @@ func (a *ReActAgent) Run(ctx context.Context, userInput string) (string, error)
|
||||
for step := 0; step < a.maxStep; step++ {
|
||||
g.Log().Infof(ctx, "ReAct step %d/%d: 调用模型...", step+1, a.maxStep)
|
||||
|
||||
// 1. LLM 思考并决定行动
|
||||
startTime := time.Now()
|
||||
result, err := a.model.Generate(ctx, messages, model.WithTools(toolInfos))
|
||||
result, err := CallChatModel(ctx, a.cfg, &ChatRequest{
|
||||
Messages: messages,
|
||||
MaxTokens: a.cfg.MaxTokens,
|
||||
Temperature: a.cfg.Temperature,
|
||||
Tools: a.tools,
|
||||
})
|
||||
elapsed := time.Since(startTime)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("step %d: 模型调用失败: %w", step, err)
|
||||
@@ -59,69 +55,66 @@ func (a *ReActAgent) Run(ctx context.Context, userInput string) (string, error)
|
||||
g.Log().Infof(ctx, "ReAct step %d/%d: 模型返回 (耗时 %v), content_len=%d, ToolCalls=%d",
|
||||
step+1, a.maxStep, elapsed, len(result.Content), len(result.ToolCalls))
|
||||
|
||||
messages = append(messages, result)
|
||||
messages = append(messages, &ChatMessage{
|
||||
Role: RoleAssistant,
|
||||
Content: result.Content,
|
||||
ToolCalls: result.ToolCalls,
|
||||
})
|
||||
|
||||
// 2. 检查是否有工具调用
|
||||
// 无工具调用 → 最终回答
|
||||
if len(result.ToolCalls) == 0 {
|
||||
// 没有工具调用 → 最终回答
|
||||
g.Log().Infof(ctx, "ReAct step %d/%d: 无工具调用,返回最终结果 (content长度=%d)", step+1, a.maxStep, len(result.Content))
|
||||
g.Log().Infof(ctx, "ReAct step %d/%d: 无工具调用,返回最终结果", step+1, a.maxStep)
|
||||
return result.Content, nil
|
||||
}
|
||||
|
||||
// 3. 执行每个工具调用
|
||||
// 执行每个工具调用
|
||||
for _, tc := range result.ToolCalls {
|
||||
tool := a.findTool(tc.Function.Name)
|
||||
tool := a.findTool(tc.Name)
|
||||
if tool == nil {
|
||||
g.Log().Warningf(ctx, "ReAct step %d: 未知工具: %s", step+1, tc.Function.Name)
|
||||
toolResultMsg := &schema.Message{
|
||||
Role: schema.Tool,
|
||||
Content: fmt.Sprintf("未知工具: %s", tc.Function.Name),
|
||||
ToolName: tc.Function.Name,
|
||||
g.Log().Warningf(ctx, "ReAct step %d: 未知工具: %s", step+1, tc.Name)
|
||||
messages = append(messages, &ChatMessage{
|
||||
Role: RoleTool,
|
||||
Content: fmt.Sprintf("未知工具: %s", tc.Name),
|
||||
Name: tc.Name,
|
||||
ToolCallID: tc.ID,
|
||||
}
|
||||
messages = append(messages, toolResultMsg)
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
// 解析参数
|
||||
var args map[string]any
|
||||
if err := json.Unmarshal([]byte(tc.Function.Arguments), &args); err != nil {
|
||||
if err := json.Unmarshal([]byte(tc.Arguments), &args); err != nil {
|
||||
g.Log().Warningf(ctx, "ReAct step %d: 参数解析失败: %v", step+1, err)
|
||||
toolResultMsg := &schema.Message{
|
||||
Role: schema.Tool,
|
||||
messages = append(messages, &ChatMessage{
|
||||
Role: RoleTool,
|
||||
Content: fmt.Sprintf("参数解析失败: %v", err),
|
||||
ToolName: tc.Function.Name,
|
||||
Name: tc.Name,
|
||||
ToolCallID: tc.ID,
|
||||
}
|
||||
messages = append(messages, toolResultMsg)
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
g.Log().Infof(ctx, "ReAct step %d: 调用工具 %s, 参数: %s", step+1, tc.Function.Name, tc.Function.Arguments)
|
||||
g.Log().Infof(ctx, "ReAct step %d: 调用工具 %s, 参数: %s", step+1, tc.Name, tc.Arguments)
|
||||
|
||||
// 执行工具
|
||||
toolStart := time.Now()
|
||||
output, err := tool.Func(ctx, args)
|
||||
toolElapsed := time.Since(toolStart)
|
||||
if err != nil {
|
||||
output = fmt.Sprintf("工具执行失败: %v", err)
|
||||
g.Log().Warningf(ctx, "ReAct step %d: 工具 %s 执行失败 (耗时 %v): %v", step+1, tc.Function.Name, toolElapsed, err)
|
||||
g.Log().Warningf(ctx, "ReAct step %d: 工具 %s 执行失败 (耗时 %v): %v", step+1, tc.Name, toolElapsed, err)
|
||||
} else {
|
||||
truncated := output
|
||||
if len(truncated) > 200 {
|
||||
truncated = truncated[:200] + "..."
|
||||
}
|
||||
g.Log().Infof(ctx, "ReAct step %d: 工具 %s 执行成功 (耗时 %v), 结果长度=%d, 预览: %s", step+1, tc.Function.Name, toolElapsed, len(output), truncated)
|
||||
g.Log().Infof(ctx, "ReAct step %d: 工具 %s 执行成功 (耗时 %v), 结果长度=%d", step+1, tc.Name, toolElapsed, len(output))
|
||||
}
|
||||
|
||||
// 4. 观察工具结果
|
||||
toolResultMsg := &schema.Message{
|
||||
Role: schema.Tool,
|
||||
messages = append(messages, &ChatMessage{
|
||||
Role: RoleTool,
|
||||
Content: output,
|
||||
Name: tc.Function.Name,
|
||||
Name: tc.Name,
|
||||
ToolCallID: tc.ID,
|
||||
}
|
||||
messages = append(messages, toolResultMsg)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,51 +11,9 @@ import (
|
||||
|
||||
"video-factory/shortdrama/dao"
|
||||
|
||||
"github.com/cloudwego/eino/schema"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
// ToolInfo 工具定义
|
||||
type ToolInfo struct {
|
||||
Name string
|
||||
Description string
|
||||
Parameters map[string]any
|
||||
Func func(ctx context.Context, args map[string]any) (string, error)
|
||||
}
|
||||
|
||||
// ToEinoToolInfo 转换为 Eino 的 ToolInfo 格式
|
||||
func (t *ToolInfo) ToEinoToolInfo() *schema.ToolInfo {
|
||||
params := make(map[string]*schema.ParameterInfo)
|
||||
if paramsMap, ok := t.Parameters["properties"].(map[string]any); ok {
|
||||
for key, val := range paramsMap {
|
||||
if prop, ok := val.(map[string]any); ok {
|
||||
desc, _ := prop["description"].(string)
|
||||
pType, _ := prop["type"].(string)
|
||||
params[key] = &schema.ParameterInfo{
|
||||
Type: schema.DataType(pType),
|
||||
Desc: desc,
|
||||
Required: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if required, ok := t.Parameters["required"].([]any); ok {
|
||||
for _, r := range required {
|
||||
if rStr, ok := r.(string); ok {
|
||||
if p, exists := params[rStr]; exists {
|
||||
p.Required = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &schema.ToolInfo{
|
||||
Name: t.Name,
|
||||
Desc: t.Description,
|
||||
ParamsOneOf: schema.NewParamsOneOfByParams(params),
|
||||
}
|
||||
}
|
||||
|
||||
// GetTools 获取 ReAct Agent 可用的所有工具
|
||||
func GetTools() []*ToolInfo {
|
||||
return []*ToolInfo{
|
||||
@@ -87,7 +45,6 @@ func parseScriptTool() *ToolInfo {
|
||||
return "", fmt.Errorf("剧本内容不能为空")
|
||||
}
|
||||
|
||||
// 按 --- 分割多集
|
||||
episodeTexts := strings.Split(rawScript, "---")
|
||||
type episodeInfo struct {
|
||||
Index int `json:"index"`
|
||||
@@ -103,7 +60,6 @@ func parseScriptTool() *ToolInfo {
|
||||
}
|
||||
lines := strings.SplitN(text, "\n", 2)
|
||||
title := strings.TrimSpace(lines[0])
|
||||
// 去掉可能的序号前缀如 "第1集"、"第一集"、"Episode 1" 等
|
||||
title = cleanEpisodeTitle(title)
|
||||
content := ""
|
||||
if len(lines) > 1 {
|
||||
@@ -168,13 +124,12 @@ func analyzeScriptForEpisodeTool() *ToolInfo {
|
||||
return "", fmt.Errorf("剧本内容不能为空")
|
||||
}
|
||||
if duration <= 0 {
|
||||
duration = 60 // 默认60秒
|
||||
duration = 60
|
||||
}
|
||||
|
||||
episodeIndex, _ := args["episode_index"].(float64)
|
||||
title, _ := args["episode_title"].(string)
|
||||
|
||||
// 按空行或场景标记分割场景
|
||||
sceneTexts := strings.Split(scriptContent, "\n\n")
|
||||
type sceneInfo struct {
|
||||
Index int `json:"index"`
|
||||
@@ -192,7 +147,6 @@ func analyzeScriptForEpisodeTool() *ToolInfo {
|
||||
sceneTexts = []string{scriptContent}
|
||||
}
|
||||
|
||||
// 推测出场演员
|
||||
var characters []string
|
||||
if charsRaw, ok := args["characters"].(string); ok && charsRaw != "" {
|
||||
var chars []struct {
|
||||
@@ -218,7 +172,6 @@ func analyzeScriptForEpisodeTool() *ToolInfo {
|
||||
sceneDur++
|
||||
}
|
||||
|
||||
// 提取第一行作为场景描述
|
||||
lines := strings.SplitN(text, "\n", 2)
|
||||
desc := strings.TrimSpace(lines[0])
|
||||
content := ""
|
||||
@@ -228,7 +181,6 @@ func analyzeScriptForEpisodeTool() *ToolInfo {
|
||||
content = desc
|
||||
}
|
||||
|
||||
// 匹配出场演员
|
||||
var sceneChars []string
|
||||
for _, c := range characters {
|
||||
if strings.Contains(text, c) {
|
||||
@@ -260,7 +212,7 @@ func analyzeScriptForEpisodeTool() *ToolInfo {
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== Tool 4: 生成场景图 ====================
|
||||
// ==================== Tool 3: 生成场景图 ====================
|
||||
|
||||
func generateSceneImageTool() *ToolInfo {
|
||||
return &ToolInfo{
|
||||
@@ -293,12 +245,9 @@ func generateSceneImageTool() *ToolInfo {
|
||||
style, _ := args["style"].(string)
|
||||
episodeIdx, _ := args["episode_index"].(float64)
|
||||
sceneIdx, _ := args["scene_index"].(float64)
|
||||
_ = style
|
||||
|
||||
prompt := fmt.Sprintf("画面描述:%s,风格:%s", visualDesc, style)
|
||||
_ = prompt // 保留供日志使用
|
||||
|
||||
// 从 DB 场景表中查找已有场景图片
|
||||
dramaId := GetDramaId(ctx)
|
||||
dramaId := GetDramaID(ctx)
|
||||
var imgBase64 string
|
||||
if dramaId > 0 && visualDesc != "" {
|
||||
scenes, err := dao.Scene.ListByDrama(ctx, dramaId)
|
||||
@@ -307,7 +256,6 @@ func generateSceneImageTool() *ToolInfo {
|
||||
if sc.ImagePath == "" {
|
||||
continue
|
||||
}
|
||||
// 匹配场景描述(双向包含匹配)
|
||||
if strings.Contains(visualDesc, sc.Description) || strings.Contains(sc.Description, visualDesc) {
|
||||
b64, err := readImageFileAsBase64(sc.ImagePath)
|
||||
if err == nil {
|
||||
@@ -341,7 +289,6 @@ func cleanEpisodeTitle(title string) string {
|
||||
prefixes := []string{"第", "Episode", "episode", "EP"}
|
||||
for _, p := range prefixes {
|
||||
if strings.HasPrefix(title, p) {
|
||||
// 去掉序号前缀后取标题部分
|
||||
parts := strings.SplitN(title, " ", 2)
|
||||
if len(parts) > 1 {
|
||||
return parts[1]
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package agent
|
||||
|
||||
import "context"
|
||||
|
||||
// ==================== 工具 ====================
|
||||
|
||||
// ToolInfo 工具定义(包含执行函数)
|
||||
type ToolInfo struct {
|
||||
Name string
|
||||
Description string
|
||||
Parameters map[string]any
|
||||
Func func(ctx context.Context, args map[string]any) (string, error)
|
||||
}
|
||||
|
||||
// ToolCall 模型请求的工具调用
|
||||
type ToolCall struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Arguments string `json:"arguments"`
|
||||
}
|
||||
|
||||
// ==================== 聊天消息 ====================
|
||||
|
||||
// ChatMessage 对话消息
|
||||
type ChatMessage struct {
|
||||
Role string `json:"role"`
|
||||
Content string `json:"content"`
|
||||
ToolCalls []*ToolCall `json:"tool_calls,omitempty"`
|
||||
ToolCallID string `json:"tool_call_id,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
}
|
||||
|
||||
// ChatRequest 聊天请求
|
||||
type ChatRequest struct {
|
||||
Messages []*ChatMessage
|
||||
MaxTokens int
|
||||
Temperature float32
|
||||
Stream bool
|
||||
Tools []*ToolInfo
|
||||
}
|
||||
|
||||
// ChatResponse 聊天响应
|
||||
type ChatResponse struct {
|
||||
Content string
|
||||
ToolCalls []*ToolCall
|
||||
}
|
||||
|
||||
// ==================== 角色常量 ====================
|
||||
|
||||
const (
|
||||
RoleSystem = "system"
|
||||
RoleUser = "user"
|
||||
RoleAssistant = "assistant"
|
||||
RoleTool = "tool"
|
||||
)
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"fmt"
|
||||
|
||||
"video-factory/shortdrama/model/dto"
|
||||
"video-factory/shortdrama/model/entity"
|
||||
"video-factory/shortdrama/service"
|
||||
)
|
||||
|
||||
@@ -14,8 +13,7 @@ type config struct{}
|
||||
var Config = new(config)
|
||||
|
||||
func (c *config) Get(ctx context.Context, req *dto.GetModelConfigReq) (res *dto.GetModelConfigRes, err error) {
|
||||
cfg := service.ConfigService.Get(ctx)
|
||||
return &dto.GetModelConfigRes{ModelConfig: cfg}, nil
|
||||
return service.ConfigService.GetResponse(ctx), nil
|
||||
}
|
||||
|
||||
func (c *config) Save(ctx context.Context, req *dto.SaveModelConfigReq) (res *struct{}, err error) {
|
||||
@@ -28,21 +26,5 @@ func (c *config) Save(ctx context.Context, req *dto.SaveModelConfigReq) (res *st
|
||||
if req.MinSingleDuration > req.MaxSingleDuration {
|
||||
return nil, fmt.Errorf("单次生成最小时长不能大于最大时长")
|
||||
}
|
||||
cfg := &entity.ModelConfig{
|
||||
ChatProvider: req.ChatProvider,
|
||||
ChatApiKey: req.ChatApiKey,
|
||||
ChatBaseUrl: req.ChatBaseUrl,
|
||||
ChatModelName: req.ChatModelName,
|
||||
MaxTokens: req.MaxTokens,
|
||||
Temperature: req.Temperature,
|
||||
ChatSchema: req.ChatSchema,
|
||||
VideoProvider: req.VideoProvider,
|
||||
VideoApiKey: req.VideoApiKey,
|
||||
VideoBaseUrl: req.VideoBaseUrl,
|
||||
VideoModelName: req.VideoModelName,
|
||||
MinSingleDuration: req.MinSingleDuration,
|
||||
MaxSingleDuration: req.MaxSingleDuration,
|
||||
VideoSchema: req.VideoSchema,
|
||||
}
|
||||
return nil, service.ConfigService.Save(ctx, cfg)
|
||||
return nil, service.ConfigService.Save(ctx, req)
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@ package dao
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"video-factory/shortdrama/consts/public"
|
||||
"video-factory/shortdrama/model/entity"
|
||||
|
||||
@@ -19,27 +17,28 @@ func init() {
|
||||
ctx := context.Background()
|
||||
if _, err := g.DB().Exec(ctx, `CREATE TABLE IF NOT EXISTS `+public.TableNameModelConfig+` (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
chat_provider TEXT NOT NULL DEFAULT '',
|
||||
chat_api_key TEXT NOT NULL DEFAULT '',
|
||||
chat_base_url TEXT NOT NULL DEFAULT '',
|
||||
chat_model_name TEXT NOT NULL DEFAULT '',
|
||||
max_tokens INTEGER NOT NULL DEFAULT 4096,
|
||||
temperature REAL NOT NULL DEFAULT 0.8,
|
||||
chat_schema TEXT NOT NULL DEFAULT '',
|
||||
video_provider TEXT NOT NULL DEFAULT '',
|
||||
video_api_key TEXT NOT NULL DEFAULT '',
|
||||
video_base_url TEXT NOT NULL DEFAULT '',
|
||||
video_model_name TEXT NOT NULL DEFAULT '',
|
||||
max_single_duration INTEGER NOT NULL DEFAULT 15,
|
||||
min_single_duration INTEGER NOT NULL DEFAULT 5,
|
||||
video_schema TEXT NOT NULL DEFAULT '',
|
||||
video_task_callback_url TEXT NOT NULL DEFAULT '',
|
||||
created_at DATETIME,
|
||||
updated_at DATETIME
|
||||
)`); err != nil {
|
||||
g.Log().Warningf(ctx, "创建模型配置表失败: %v", err)
|
||||
}
|
||||
// 迁移:清理孤儿字段
|
||||
|
||||
// 清理旧字段
|
||||
for _, col := range []string{
|
||||
"chat_provider", "video_provider",
|
||||
"video_no_duration_support",
|
||||
"video_model_category", "image_model_name",
|
||||
"chat_params", "video_params",
|
||||
@@ -53,12 +52,11 @@ func init() {
|
||||
g.Log().Debugf(ctx, "删除孤儿列 %s 失败(可能已删除): %v", col, err)
|
||||
}
|
||||
}
|
||||
// 迁移:补充新字段
|
||||
// 补充新字段
|
||||
for _, col := range []string{
|
||||
"chat_provider",
|
||||
"video_provider",
|
||||
"chat_schema",
|
||||
"video_schema",
|
||||
"video_task_callback_url",
|
||||
} {
|
||||
if _, err := g.DB().Exec(ctx, `ALTER TABLE `+public.TableNameModelConfig+` ADD COLUMN `+col+` TEXT NOT NULL DEFAULT ''`); err != nil {
|
||||
g.Log().Debugf(ctx, "添加列 %s 失败(可能已存在): %v", col, err)
|
||||
@@ -76,12 +74,7 @@ func (d *modelConfigDao) GetFirst(ctx context.Context) (res *entity.ModelConfig,
|
||||
return nil, nil
|
||||
}
|
||||
res = new(entity.ModelConfig)
|
||||
if err := r.Struct(&res); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// SQLite TEXT → *json.RawMessage 反序列化
|
||||
res.ChatSchema = textToJSONPtr(r["chat_schema"].String())
|
||||
res.VideoSchema = textToJSONPtr(r["video_schema"].String())
|
||||
err = r.Struct(&res)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -100,9 +93,6 @@ func (d *modelConfigDao) Save(ctx context.Context, data *entity.ModelConfig) err
|
||||
delete(m, "id")
|
||||
delete(m, "created_at")
|
||||
delete(m, "updated_at")
|
||||
// *json.RawMessage → SQLite TEXT 序列化
|
||||
m["chat_schema"] = jsonPtrToText(data.ChatSchema)
|
||||
m["video_schema"] = jsonPtrToText(data.VideoSchema)
|
||||
_, err = g.DB().Model(public.TableNameModelConfig).Ctx(ctx).Data(m).Insert()
|
||||
return err
|
||||
}
|
||||
@@ -119,22 +109,3 @@ func (d *modelConfigDao) UpdateField(ctx context.Context, field string, value in
|
||||
_, err = g.DB().Model(public.TableNameModelConfig).Ctx(ctx).Data(g.Map{field: value}).Where("id", existing.Id).Update()
|
||||
return err
|
||||
}
|
||||
|
||||
// ==================== JSON 序列化/反序列化辅助 ====================
|
||||
|
||||
// textToJSONPtr 将 SQLite TEXT 转换为 *json.RawMessage
|
||||
func textToJSONPtr(s string) *json.RawMessage {
|
||||
if s == "" {
|
||||
return nil
|
||||
}
|
||||
raw := json.RawMessage(s)
|
||||
return &raw
|
||||
}
|
||||
|
||||
// jsonPtrToText 将 *json.RawMessage 转换为 SQLite TEXT
|
||||
func jsonPtrToText(j *json.RawMessage) string {
|
||||
if j == nil {
|
||||
return ""
|
||||
}
|
||||
return string(*j)
|
||||
}
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
package adapter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/cloudwego/eino/components/model"
|
||||
)
|
||||
|
||||
// ==================== Provider Types ====================
|
||||
|
||||
// ChatProvider 对话模型供应商
|
||||
type ChatProvider string
|
||||
|
||||
const (
|
||||
ChatProviderQwen ChatProvider = "qwen" // 通义千问(使用Eino专用适配器)
|
||||
ChatProviderDeepSeek ChatProvider = "deepseek" // DeepSeek(OpenAI兼容协议)
|
||||
ChatProviderDoubao ChatProvider = "doubao" // 豆包(OpenAI兼容协议)
|
||||
ChatProviderGLM ChatProvider = "glm" // 智谱GLM(OpenAI兼容协议)
|
||||
ChatProviderErnie ChatProvider = "ernie" // 文心ERNIE(OpenAI兼容协议)
|
||||
ChatProviderOpenAI ChatProvider = "openai" // OpenAI(GPT/o1等)
|
||||
)
|
||||
|
||||
// ==================== Config ====================
|
||||
|
||||
// ChatModelConfig 对话模型适配器统一配置
|
||||
type ChatModelConfig struct {
|
||||
Provider ChatProvider // 模型供应商
|
||||
ModelName string // 模型名称
|
||||
APIKey string // API密钥
|
||||
BaseURL string // API地址
|
||||
MaxTokens int // 最大Token数
|
||||
Temperature float32 // 温度参数
|
||||
Timeout time.Duration // HTTP请求超时(0表示默认)
|
||||
}
|
||||
|
||||
// ==================== Factory ====================
|
||||
|
||||
// NewChatModel 根据配置创建对应的对话模型适配器,返回 Eino ChatModel 接口
|
||||
func NewChatModel(ctx context.Context, cfg *ChatModelConfig) (model.ChatModel, error) {
|
||||
if cfg == nil {
|
||||
return nil, fmt.Errorf("模型配置不能为空")
|
||||
}
|
||||
if cfg.APIKey == "" {
|
||||
return nil, fmt.Errorf("APIKey 未配置")
|
||||
}
|
||||
if cfg.ModelName == "" {
|
||||
return nil, fmt.Errorf("模型名称未配置")
|
||||
}
|
||||
if cfg.BaseURL == "" {
|
||||
return nil, fmt.Errorf("API 地址未配置")
|
||||
}
|
||||
|
||||
// 设置默认值
|
||||
maxTokens := cfg.MaxTokens
|
||||
if maxTokens <= 0 {
|
||||
maxTokens = 4096
|
||||
}
|
||||
temperature := cfg.Temperature
|
||||
if temperature <= 0 {
|
||||
temperature = 0.8
|
||||
}
|
||||
|
||||
adapterCfg := &ChatModelConfig{
|
||||
Provider: cfg.Provider,
|
||||
ModelName: cfg.ModelName,
|
||||
APIKey: cfg.APIKey,
|
||||
BaseURL: cfg.BaseURL,
|
||||
MaxTokens: maxTokens,
|
||||
Temperature: temperature,
|
||||
Timeout: cfg.Timeout,
|
||||
}
|
||||
|
||||
switch cfg.Provider {
|
||||
case ChatProviderQwen:
|
||||
return newQwenChatModel(ctx, adapterCfg)
|
||||
default:
|
||||
return newOpenAIChatModel(ctx, adapterCfg)
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package adapter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/cloudwego/eino-ext/libs/acl/openai"
|
||||
"github.com/cloudwego/eino/components/model"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
// newOpenAIChatModel 创建 OpenAI 兼容协议对话模型
|
||||
// 适用于:DeepSeek、GLM(智谱)、ERNIE(文心)、Moonshot(Kimi)、Minimax、Stepfun(阶跃星辰)、
|
||||
//
|
||||
// Baichuan(百川)、GPT系列、Claude系列等所有兼容 OpenAI API 格式的模型
|
||||
func newOpenAIChatModel(ctx context.Context, cfg *ChatModelConfig) (model.ChatModel, error) {
|
||||
httpClient := http.DefaultClient
|
||||
if cfg.Timeout > 0 {
|
||||
httpClient = &http.Client{Timeout: cfg.Timeout}
|
||||
}
|
||||
|
||||
config := &openai.Config{
|
||||
APIKey: cfg.APIKey,
|
||||
Model: cfg.ModelName,
|
||||
BaseURL: cfg.BaseURL,
|
||||
HTTPClient: httpClient,
|
||||
MaxTokens: gconv.PtrInt(cfg.MaxTokens),
|
||||
Temperature: gconv.PtrFloat32(cfg.Temperature),
|
||||
}
|
||||
|
||||
client, err := openai.NewClient(ctx, config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("创建OpenAI兼容模型失败: %w", err)
|
||||
}
|
||||
return client, nil
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package adapter
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cloudwego/eino-ext/components/model/qwen"
|
||||
"github.com/cloudwego/eino/components/model"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
// newQwenChatModel 创建通义千问对话模型(使用 Eino 专用适配器)
|
||||
func newQwenChatModel(ctx context.Context, cfg *ChatModelConfig) (model.ChatModel, error) {
|
||||
config := &qwen.ChatModelConfig{
|
||||
APIKey: cfg.APIKey,
|
||||
Model: cfg.ModelName,
|
||||
BaseURL: cfg.BaseURL,
|
||||
MaxTokens: gconv.PtrInt(cfg.MaxTokens),
|
||||
Temperature: gconv.PtrFloat32(cfg.Temperature),
|
||||
Timeout: cfg.Timeout,
|
||||
}
|
||||
return qwen.NewChatModel(ctx, config)
|
||||
}
|
||||
@@ -94,21 +94,16 @@ func parseInt(s string) (int, error) {
|
||||
|
||||
// ==================== Factory ====================
|
||||
|
||||
// NewVideoAdapter 根据供应商类型创建视频模型适配器
|
||||
func NewVideoAdapter(provider VideoProvider, apiKey, baseURL, modelName string) VideoAdapter {
|
||||
switch provider {
|
||||
case VideoProviderDashScope:
|
||||
return NewDashScopeAdapter(apiKey, baseURL, modelName)
|
||||
case VideoProviderVolcano:
|
||||
// NewVideoAdapter 根据模型名称自动识别供应商并创建视频模型适配器
|
||||
func NewVideoAdapter(apiKey, baseURL, modelName string) VideoAdapter {
|
||||
modelLower := strings.ToLower(modelName)
|
||||
baseURLLower := strings.ToLower(baseURL)
|
||||
|
||||
// 火山引擎(豆包视频生成)
|
||||
if strings.Contains(modelLower, "doubao") || strings.Contains(modelLower, "seedo") ||
|
||||
strings.Contains(baseURLLower, "volc") || strings.Contains(baseURLLower, "volcano") {
|
||||
return NewVolcanoAdapter(apiKey, baseURL, modelName)
|
||||
// 后续可在此添加:
|
||||
// case VideoProviderKling:
|
||||
// return NewKlingAdapter(apiKey, baseURL, modelName)
|
||||
// case VideoProviderRunway:
|
||||
// return NewRunwayAdapter(apiKey, baseURL, modelName)
|
||||
// case VideoProviderPika:
|
||||
// return NewPikaAdapter(apiKey, baseURL, modelName)
|
||||
default:
|
||||
return NewDashScopeAdapter(apiKey, baseURL, modelName)
|
||||
}
|
||||
// 默认使用通义万相(阿里云百炼)
|
||||
return NewDashScopeAdapter(apiKey, baseURL, modelName)
|
||||
}
|
||||
|
||||
@@ -1,35 +1,38 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"video-factory/shortdrama/model/entity"
|
||||
|
||||
"github.com/gogf/gf/v2/encoding/gjson"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
type GetModelConfigReq struct {
|
||||
g.Meta `path:"/model" method:"get" tags:"模型配置" summary:"获取模型配置"`
|
||||
*entity.ModelConfig
|
||||
ChatSchema *gjson.Json `json:"chatSchema"`
|
||||
VideoSchema *gjson.Json `json:"videoSchema"`
|
||||
}
|
||||
|
||||
type GetModelConfigRes struct {
|
||||
*entity.ModelConfig
|
||||
ChatSchema *gjson.Json `json:"chatSchema"`
|
||||
VideoSchema *gjson.Json `json:"videoSchema"`
|
||||
}
|
||||
|
||||
type SaveModelConfigReq struct {
|
||||
g.Meta `path:"/model" method:"post" tags:"模型配置" summary:"保存模型配置"`
|
||||
ChatProvider string `json:"chatProvider" dc:"对话模型供应商(qwen/deepseek/doubao/glm/ernie/openai)"`
|
||||
ChatApiKey string `v:"required" json:"chatApiKey" dc:"对话模型API密钥"`
|
||||
ChatBaseUrl string `v:"required|url" json:"chatBaseUrl" dc:"对话模型API接口地址"`
|
||||
ChatModelName string `v:"required" json:"chatModelName" dc:"对话模型名称"`
|
||||
MaxTokens int `v:"required" json:"maxTokens" dc:"最大Token数"`
|
||||
Temperature float64 `v:"required" json:"temperature" dc:"温度参数"`
|
||||
ChatSchema *json.RawMessage `json:"chatSchema" dc:"对话模型schema"`
|
||||
VideoProvider string `json:"videoProvider" dc:"视频模型供应商(dashscope/kling/runway),空则自动检测"`
|
||||
VideoApiKey string `v:"required" json:"videoApiKey" dc:"视频模型API密钥"`
|
||||
VideoBaseUrl string `v:"required|url" json:"videoBaseUrl" dc:"视频模型API接口地址"`
|
||||
VideoModelName string `v:"required" json:"videoModelName" dc:"视频模型名称"`
|
||||
MaxSingleDuration int `v:"required" json:"maxSingleDuration" dc:"单段最大时长"`
|
||||
MinSingleDuration int `v:"required" json:"minSingleDuration" dc:"单段最小时长"`
|
||||
VideoSchema *json.RawMessage `json:"videoSchema" dc:"视频生成模型schema"`
|
||||
g.Meta `path:"/model" method:"post" tags:"模型配置" summary:"保存模型配置"`
|
||||
ChatApiKey string `v:"required" json:"chatApiKey" dc:"对话模型API密钥"`
|
||||
ChatBaseUrl string `v:"required|url" json:"chatBaseUrl" dc:"对话模型API接口地址"`
|
||||
ChatModelName string `v:"required" json:"chatModelName" dc:"对话模型名称"`
|
||||
MaxTokens int `v:"required" json:"maxTokens" dc:"最大Token数"`
|
||||
Temperature float64 `v:"required" json:"temperature" dc:"温度参数"`
|
||||
ChatSchema *gjson.Json `json:"chatSchema" dc:"对话模型schema"`
|
||||
VideoApiKey string `v:"required" json:"videoApiKey" dc:"视频模型API密钥"`
|
||||
VideoBaseUrl string `v:"required|url" json:"videoBaseUrl" dc:"视频模型API接口地址"`
|
||||
VideoModelName string `v:"required" json:"videoModelName" dc:"视频模型名称"`
|
||||
MaxSingleDuration int `v:"required" json:"maxSingleDuration" dc:"单段最大时长"`
|
||||
MinSingleDuration int `v:"required" json:"minSingleDuration" dc:"单段最小时长"`
|
||||
VideoSchema *gjson.Json `json:"videoSchema" dc:"视频生成模型schema"`
|
||||
VideoTaskCallbackUrl string `json:"videoTaskCallbackUrl" dc:"视频生成任务回调地址"`
|
||||
}
|
||||
|
||||
@@ -1,27 +1,24 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
)
|
||||
|
||||
type ModelConfig struct {
|
||||
Id int64 `orm:"id" json:"id" dc:"配置ID"`
|
||||
ChatProvider string `orm:"chat_provider" json:"chatProvider" dc:"对话模型供应商(qwen/deepseek/doubao/glm/ernie/openai)"`
|
||||
ChatApiKey string `orm:"chat_api_key" json:"chatApiKey" dc:"对话模型API密钥"`
|
||||
ChatBaseUrl string `orm:"chat_base_url" json:"chatBaseUrl" dc:"对话模型接口地址"`
|
||||
ChatModelName string `orm:"chat_model_name" json:"chatModelName" dc:"对话模型名称"`
|
||||
MaxTokens int `orm:"max_tokens" json:"maxTokens" dc:"最大Token数"`
|
||||
Temperature float64 `orm:"temperature" json:"temperature" dc:"温度参数"`
|
||||
ChatSchema *json.RawMessage `orm:"chat_schema" json:"chatSchema" dc:"对话模型schema"`
|
||||
VideoProvider string `orm:"video_provider" json:"videoProvider" dc:"视频模型供应商(dashscope/kling/runway)"`
|
||||
VideoApiKey string `orm:"video_api_key" json:"videoApiKey" dc:"视频模型API密钥"`
|
||||
VideoBaseUrl string `orm:"video_base_url" json:"videoBaseUrl" dc:"视频模型接口地址"`
|
||||
VideoModelName string `orm:"video_model_name" json:"videoModelName" dc:"视频模型名称"`
|
||||
MaxSingleDuration int `orm:"max_single_duration" json:"maxSingleDuration" dc:"单段最大时长"`
|
||||
MinSingleDuration int `orm:"min_single_duration" json:"minSingleDuration" dc:"单段最小时长"`
|
||||
VideoSchema *json.RawMessage `orm:"video_schema" json:"videoSchema" dc:"视频生成模型schema"`
|
||||
CreatedAt *gtime.Time `orm:"created_at" json:"createdAt" dc:"创建时间"`
|
||||
UpdatedAt *gtime.Time `orm:"updated_at" json:"updatedAt" dc:"更新时间"`
|
||||
Id int64 `orm:"id" json:"id" dc:"配置ID"`
|
||||
ChatApiKey string `orm:"chat_api_key" json:"chatApiKey" dc:"对话模型API密钥"`
|
||||
ChatBaseUrl string `orm:"chat_base_url" json:"chatBaseUrl" dc:"对话模型接口地址"`
|
||||
ChatModelName string `orm:"chat_model_name" json:"chatModelName" dc:"对话模型名称"`
|
||||
MaxTokens int `orm:"max_tokens" json:"maxTokens" dc:"最大Token数"`
|
||||
Temperature float64 `orm:"temperature" json:"temperature" dc:"温度参数"`
|
||||
ChatSchema string `orm:"chat_schema" json:"chatSchema" dc:"对话模型schema"`
|
||||
VideoApiKey string `orm:"video_api_key" json:"videoApiKey" dc:"视频模型API密钥"`
|
||||
VideoBaseUrl string `orm:"video_base_url" json:"videoBaseUrl" dc:"视频模型接口地址"`
|
||||
VideoModelName string `orm:"video_model_name" json:"videoModelName" dc:"视频模型名称"`
|
||||
MaxSingleDuration int `orm:"max_single_duration" json:"maxSingleDuration" dc:"单段最大时长"`
|
||||
MinSingleDuration int `orm:"min_single_duration" json:"minSingleDuration" dc:"单段最小时长"`
|
||||
VideoSchema string `orm:"video_schema" json:"videoSchema" dc:"视频生成模型schema"`
|
||||
VideoTaskCallbackUrl string `orm:"video_task_callback_url" json:"videoTaskCallbackUrl" dc:"视频生成任务回调地址"`
|
||||
CreatedAt *gtime.Time `orm:"created_at" json:"createdAt" dc:"创建时间"`
|
||||
UpdatedAt *gtime.Time `orm:"updated_at" json:"updatedAt" dc:"更新时间"`
|
||||
}
|
||||
|
||||
@@ -8,10 +8,12 @@ import (
|
||||
"strings"
|
||||
|
||||
"video-factory/shortdrama/dao"
|
||||
"video-factory/shortdrama/model/dto"
|
||||
"video-factory/shortdrama/model/entity"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gcache"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
const cacheKeyConfig = "model_config"
|
||||
@@ -21,7 +23,7 @@ type configService struct{}
|
||||
|
||||
var ConfigService = new(configService)
|
||||
|
||||
// Get 获取模型配置(优先走内存缓存)
|
||||
// Get 获取模型配置实体(供内部调用,schema 为 string 类型)
|
||||
func (s *configService) Get(ctx context.Context) *entity.ModelConfig {
|
||||
if v, err := gcache.Get(ctx, cacheKeyConfig); err == nil && !v.IsNil() {
|
||||
return v.Val().(*entity.ModelConfig)
|
||||
@@ -38,67 +40,50 @@ func (s *configService) Get(ctx context.Context) *entity.ModelConfig {
|
||||
return m
|
||||
}
|
||||
|
||||
// Save 保存模型配置:先合并用户输入,再统一写入数据库
|
||||
func (s *configService) Save(ctx context.Context, cfg *entity.ModelConfig) error {
|
||||
if existing, _ := dao.ModelConfig.GetFirst(ctx); existing != nil {
|
||||
// 已有配置:把现有值作为默认值填充到 cfg,当前请求携带的值优先
|
||||
if cfg.ChatProvider == "" {
|
||||
cfg.ChatProvider = existing.ChatProvider
|
||||
}
|
||||
if cfg.ChatApiKey == "" {
|
||||
cfg.ChatApiKey = existing.ChatApiKey
|
||||
}
|
||||
if cfg.ChatBaseUrl == "" {
|
||||
cfg.ChatBaseUrl = existing.ChatBaseUrl
|
||||
}
|
||||
if cfg.ChatModelName == "" {
|
||||
cfg.ChatModelName = existing.ChatModelName
|
||||
}
|
||||
if cfg.VideoProvider == "" {
|
||||
cfg.VideoProvider = existing.VideoProvider
|
||||
}
|
||||
if cfg.VideoApiKey == "" {
|
||||
cfg.VideoApiKey = existing.VideoApiKey
|
||||
}
|
||||
if cfg.VideoBaseUrl == "" {
|
||||
cfg.VideoBaseUrl = existing.VideoBaseUrl
|
||||
}
|
||||
if cfg.VideoModelName == "" {
|
||||
cfg.VideoModelName = existing.VideoModelName
|
||||
}
|
||||
if cfg.MaxTokens == 0 && existing.MaxTokens > 0 {
|
||||
cfg.MaxTokens = existing.MaxTokens
|
||||
}
|
||||
if cfg.Temperature == 0 && existing.Temperature > 0 {
|
||||
cfg.Temperature = existing.Temperature
|
||||
}
|
||||
if cfg.MinSingleDuration == 0 && existing.MinSingleDuration > 0 {
|
||||
cfg.MinSingleDuration = existing.MinSingleDuration
|
||||
}
|
||||
if cfg.MaxSingleDuration == 0 && existing.MaxSingleDuration > 0 {
|
||||
cfg.MaxSingleDuration = existing.MaxSingleDuration
|
||||
}
|
||||
// GetResponse 获取模型配置 DTO(给 Controller 返回前端,schema 为 *json.RawMessage)
|
||||
func (s *configService) GetResponse(ctx context.Context) *dto.GetModelConfigRes {
|
||||
cfg := s.Get(ctx)
|
||||
res := &dto.GetModelConfigRes{ModelConfig: cfg}
|
||||
gconv.Struct(cfg.ChatSchema, &res.ChatSchema)
|
||||
gconv.Struct(cfg.VideoSchema, &res.VideoSchema)
|
||||
return res
|
||||
}
|
||||
|
||||
// Save 保存模型配置(接收 DTO,内部用 gconv.Struct 转换)
|
||||
func (s *configService) Save(ctx context.Context, req *dto.SaveModelConfigReq) error {
|
||||
existing, _ := dao.ModelConfig.GetFirst(ctx)
|
||||
|
||||
cfg := new(entity.ModelConfig)
|
||||
if existing != nil {
|
||||
gconv.Struct(existing, cfg)
|
||||
}
|
||||
// DTO → Entity(同名按字段名映射,schema 除外)
|
||||
gconv.Struct(req, cfg)
|
||||
|
||||
// 特殊处理:*json.RawMessage → string
|
||||
if req.ChatSchema != nil {
|
||||
cfg.ChatSchema = req.ChatSchema.String()
|
||||
}
|
||||
if req.VideoSchema != nil {
|
||||
cfg.VideoSchema = req.VideoSchema.String()
|
||||
}
|
||||
|
||||
// 查询官方API获取推荐参数(直接在 cfg 上修改)
|
||||
syncModelDurationFromAPI(ctx, cfg)
|
||||
|
||||
// 统一写入数据库
|
||||
if err := dao.ModelConfig.Save(ctx, cfg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_ = gcache.Set(ctx, cacheKeyConfig, cfg, 0)
|
||||
return nil
|
||||
}
|
||||
|
||||
// syncModelDurationFromAPI 查询模型API,获取模型支持的参数和时长范围
|
||||
// 并自动更新模型配置中的各项参数
|
||||
func syncModelDurationFromAPI(ctx context.Context, cfg *entity.ModelConfig) {
|
||||
if cfg.VideoApiKey == "" || cfg.VideoBaseUrl == "" || cfg.VideoModelName == "" {
|
||||
return
|
||||
}
|
||||
|
||||
// ----- 视频模型参数默认值 -----
|
||||
if cfg.MinSingleDuration <= 0 {
|
||||
cfg.MinSingleDuration = 2
|
||||
g.Log().Infof(ctx, "使用默认单段最小时长: 2 秒")
|
||||
@@ -107,19 +92,15 @@ func syncModelDurationFromAPI(ctx context.Context, cfg *entity.ModelConfig) {
|
||||
cfg.MaxSingleDuration = 15
|
||||
g.Log().Infof(ctx, "使用默认单段最大时长: 15 秒")
|
||||
}
|
||||
|
||||
// Temperature 默认值
|
||||
if cfg.Temperature <= 0 {
|
||||
cfg.Temperature = 0.85
|
||||
g.Log().Infof(ctx, "使用默认 Temperature: 0.85")
|
||||
}
|
||||
|
||||
// ----- 查询对话模型参数 -----
|
||||
if cfg.ChatApiKey == "" || cfg.ChatBaseUrl == "" || cfg.ChatModelName == "" {
|
||||
return
|
||||
}
|
||||
|
||||
// 从 ChatBaseUrl 确定模型列表 API 地址
|
||||
modelsURL := strings.TrimRight(cfg.ChatBaseUrl, "/") + "/models"
|
||||
chatReq, err := http.NewRequestWithContext(ctx, "GET", modelsURL, nil)
|
||||
if err != nil {
|
||||
@@ -137,7 +118,6 @@ func syncModelDurationFromAPI(ctx context.Context, cfg *entity.ModelConfig) {
|
||||
defer chatResp.Body.Close()
|
||||
|
||||
chatBody, _ := io.ReadAll(chatResp.Body)
|
||||
|
||||
var chatModelList struct {
|
||||
Object string `json:"object"`
|
||||
Data []struct {
|
||||
@@ -155,21 +135,18 @@ func syncModelDurationFromAPI(ctx context.Context, cfg *entity.ModelConfig) {
|
||||
return
|
||||
}
|
||||
|
||||
// 根据模型名称模式推断 max_tokens 和 temperature(仅当用户未设置时)
|
||||
chatModelName := strings.ToLower(cfg.ChatModelName)
|
||||
|
||||
if cfg.MaxTokens <= 0 {
|
||||
cfg.MaxTokens = inferChatMaxTokens(chatModelName)
|
||||
g.Log().Infof(ctx, "根据模型名称推断 max_tokens: %d(用户在表单中可手动修改)", cfg.MaxTokens)
|
||||
}
|
||||
|
||||
if cfg.Temperature <= 0 {
|
||||
cfg.Temperature = inferChatTemperature(chatModelName)
|
||||
g.Log().Infof(ctx, "根据模型名称推断 Temperature: %.2f(用户在表单中可手动修改)", cfg.Temperature)
|
||||
}
|
||||
}
|
||||
|
||||
// inferChatMaxTokens 根据模型名推断合适的 max_tokens
|
||||
func inferChatMaxTokens(modelName string) int {
|
||||
switch {
|
||||
case strings.Contains(modelName, "qwen3"):
|
||||
@@ -199,7 +176,6 @@ func inferChatMaxTokens(modelName string) int {
|
||||
}
|
||||
}
|
||||
|
||||
// inferChatTemperature 根据模型名推断合适的 temperature
|
||||
func inferChatTemperature(modelName string) float64 {
|
||||
switch {
|
||||
case strings.Contains(modelName, "qwen3"):
|
||||
|
||||
@@ -232,11 +232,7 @@ func (s *dramaService) GenerateEpisode(ctx context.Context, dramaId, epId int64,
|
||||
g.Log().Infof(ctx, "预加载引用数据: %d个演员, %d个场景, %d个道具, %d个引用",
|
||||
len(genCtx2.Characters), len(genCtx2.Scenes), len(genCtx2.Props), len(genCtx2.OrderedRefs))
|
||||
|
||||
genCtx := agent.WithModelConfig(context.Background(), &agent.ModelConfig{
|
||||
APIKey: modelCfg.ChatApiKey,
|
||||
BaseURL: modelCfg.ChatBaseUrl,
|
||||
})
|
||||
genCtx = agent.WithDramaId(genCtx, d.Id)
|
||||
genCtx := agent.WithDramaID(context.Background(), d.Id)
|
||||
|
||||
segDurs := calcSegDurs(d.EpisodeDuration, modelCfg)
|
||||
numSegments := len(segDurs)
|
||||
@@ -451,15 +447,12 @@ func (s *dramaService) generateSegment(ctx context.Context, d *entity.Drama, ep
|
||||
segIdx, segDur, segStartTime, totalSegs int, modelCfg *entity.ModelConfig, feedback string,
|
||||
genCtx *GenerationContext) (string, error) {
|
||||
|
||||
chatModel, err := agent.NewChatModel(ctx, &agent.ModelConfig{
|
||||
chatCfg := &agent.ModelConfig{
|
||||
ModelName: modelCfg.ChatModelName,
|
||||
APIKey: modelCfg.ChatApiKey,
|
||||
BaseURL: modelCfg.ChatBaseUrl,
|
||||
MaxTokens: modelCfg.MaxTokens,
|
||||
Temperature: float32(modelCfg.Temperature),
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
systemPrompt := s.buildSegPrompt(ctx, d, ep, segIdx, segDur, totalSegs, modelCfg)
|
||||
@@ -501,15 +494,9 @@ func (s *dramaService) generateSegment(ctx context.Context, d *entity.Drama, ep
|
||||
}
|
||||
g.Log().Infof(ctx, "Agent动态调整 max_tokens=%d (输入约%d tokens)", effectiveMaxTokens, estimatedInputTokens)
|
||||
}
|
||||
chatModel, _ = agent.NewChatModel(ctx, &agent.ModelConfig{
|
||||
ModelName: modelCfg.ChatModelName,
|
||||
APIKey: modelCfg.ChatApiKey,
|
||||
BaseURL: modelCfg.ChatBaseUrl,
|
||||
MaxTokens: effectiveMaxTokens,
|
||||
Temperature: float32(modelCfg.Temperature),
|
||||
})
|
||||
chatCfg.MaxTokens = effectiveMaxTokens
|
||||
|
||||
reactAgent := agent.NewReActAgent(ctx, chatModel, agent.GetTools(), systemPrompt, 15)
|
||||
reactAgent := agent.NewReActAgent(chatCfg, agent.GetTools(), systemPrompt, 15)
|
||||
result, err := reactAgent.Run(ctx, userInput)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -774,11 +761,7 @@ func (s *dramaService) FeedbackSegment(ctx context.Context, taskId int64, feedba
|
||||
|
||||
go func() {
|
||||
genCtx := context.Background()
|
||||
genCtx = agent.WithModelConfig(genCtx, &agent.ModelConfig{
|
||||
APIKey: modelCfg.ChatApiKey,
|
||||
BaseURL: modelCfg.ChatBaseUrl,
|
||||
})
|
||||
genCtx = agent.WithDramaId(genCtx, d.Id)
|
||||
genCtx = agent.WithDramaID(genCtx, d.Id)
|
||||
g.Log().Infof(genCtx, "第%d集第%d段根据反馈重新生成", ep.Index, task.SegmentIdx+1)
|
||||
if err := s.generateOneSegment(genCtx, d, ep, taskId, task.SegmentIdx, segDur, feedback, feedbackGenCtx); err != nil {
|
||||
g.Log().Errorf(genCtx, "第%d集第%d段重新生成失败: %v", ep.Index, task.SegmentIdx+1, err)
|
||||
@@ -982,7 +965,7 @@ func (s *dramaService) pollPendingVideos(ctx context.Context) {
|
||||
|
||||
// pollVideoTaskOnce 单次查询视频任务状态(使用视频适配器)
|
||||
func (s *dramaService) pollVideoTaskOnce(ctx context.Context, modelCfg *entity.ModelConfig, taskId string) (string, error) {
|
||||
videoAdapter := adapter.NewVideoAdapter(adapter.VideoProvider(modelCfg.VideoProvider), modelCfg.VideoApiKey, modelCfg.VideoBaseUrl, modelCfg.VideoModelName)
|
||||
videoAdapter := adapter.NewVideoAdapter(modelCfg.VideoApiKey, modelCfg.VideoBaseUrl, modelCfg.VideoModelName)
|
||||
|
||||
res, err := videoAdapter.Query(ctx, taskId)
|
||||
if err != nil {
|
||||
@@ -1017,7 +1000,7 @@ func getVideoAdapter(ctx context.Context) (adapter.VideoAdapter, error) {
|
||||
if modelCfg.VideoApiKey == "" || modelCfg.VideoBaseUrl == "" || modelCfg.VideoModelName == "" {
|
||||
return nil, fmt.Errorf("视频模型未配置")
|
||||
}
|
||||
return adapter.NewVideoAdapter(adapter.VideoProvider(modelCfg.VideoProvider), modelCfg.VideoApiKey, modelCfg.VideoBaseUrl, modelCfg.VideoModelName), nil
|
||||
return adapter.NewVideoAdapter(modelCfg.VideoApiKey, modelCfg.VideoBaseUrl, modelCfg.VideoModelName), nil
|
||||
}
|
||||
|
||||
// submitVideoTask 提交视频合成任务(使用视频适配器)
|
||||
|
||||
@@ -14,7 +14,6 @@ import (
|
||||
"video-factory/shortdrama/dao"
|
||||
"video-factory/shortdrama/model/entity"
|
||||
|
||||
"github.com/cloudwego/eino/schema"
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
@@ -166,27 +165,26 @@ func (s *dramaService) GenerateScript(ctx context.Context, dramaId int64, episod
|
||||
return
|
||||
}
|
||||
|
||||
// 创建聊天模型
|
||||
chatModel, err := agent.NewChatModel(ctx, &agent.ModelConfig{
|
||||
systemPrompt := PromptService.GetScriptGenerationPrompt(ctx)
|
||||
userInput := s.buildScriptGenUserInput(d, episodeTitle, description, genCtx)
|
||||
|
||||
chatCfg := &agent.ModelConfig{
|
||||
ModelName: modelCfg.ChatModelName,
|
||||
APIKey: modelCfg.ChatApiKey,
|
||||
BaseURL: modelCfg.ChatBaseUrl,
|
||||
MaxTokens: modelCfg.MaxTokens,
|
||||
Temperature: float32(modelCfg.Temperature),
|
||||
Timeout: 3 * time.Minute,
|
||||
}
|
||||
|
||||
messages := []*agent.ChatMessage{
|
||||
{Role: agent.RoleSystem, Content: systemPrompt},
|
||||
{Role: agent.RoleUser, Content: userInput},
|
||||
}
|
||||
result, err := agent.CallChatModel(ctx, chatCfg, &agent.ChatRequest{
|
||||
Messages: messages,
|
||||
MaxTokens: chatCfg.MaxTokens,
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
systemPrompt := PromptService.GetScriptGenerationPrompt(ctx)
|
||||
userInput := s.buildScriptGenUserInput(d, episodeTitle, description, genCtx)
|
||||
|
||||
messages := []*schema.Message{
|
||||
schema.SystemMessage(systemPrompt),
|
||||
schema.UserMessage(userInput),
|
||||
}
|
||||
result, err := chatModel.Generate(ctx, messages)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("生成脚本失败: %w", err)
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user