diff --git a/go.mod b/go.mod index 3d46eed..50695dd 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index ee8efec..57341e6 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/short_drama.db b/short_drama.db index 6547712..2e742e4 100644 Binary files a/short_drama.db and b/short_drama.db differ diff --git a/shortdrama/agent/chat_model.go b/shortdrama/agent/chat_model.go index 3699ff4..2241e29 100644 --- a/shortdrama/agent/chat_model.go +++ b/shortdrama/agent/chat_model.go @@ -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 } diff --git a/shortdrama/agent/context.go b/shortdrama/agent/context.go new file mode 100644 index 0000000..a5e241b --- /dev/null +++ b/shortdrama/agent/context.go @@ -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 +} diff --git a/shortdrama/agent/react_agent.go b/shortdrama/agent/react_agent.go index bf84e05..881c230 100644 --- a/shortdrama/agent/react_agent.go +++ b/shortdrama/agent/react_agent.go @@ -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) + }) } } diff --git a/shortdrama/agent/tools.go b/shortdrama/agent/tools.go index 44c1710..ff83354 100644 --- a/shortdrama/agent/tools.go +++ b/shortdrama/agent/tools.go @@ -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] diff --git a/shortdrama/agent/types.go b/shortdrama/agent/types.go new file mode 100644 index 0000000..3fe6a67 --- /dev/null +++ b/shortdrama/agent/types.go @@ -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" +) diff --git a/shortdrama/controller/config_controller.go b/shortdrama/controller/config_controller.go index a997141..cbaac5a 100644 --- a/shortdrama/controller/config_controller.go +++ b/shortdrama/controller/config_controller.go @@ -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) } diff --git a/shortdrama/dao/model_config_dao.go b/shortdrama/dao/model_config_dao.go index b28460c..0b7183b 100644 --- a/shortdrama/dao/model_config_dao.go +++ b/shortdrama/dao/model_config_dao.go @@ -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) -} diff --git a/shortdrama/model/adapter/chat.go b/shortdrama/model/adapter/chat.go deleted file mode 100644 index dba5aa0..0000000 --- a/shortdrama/model/adapter/chat.go +++ /dev/null @@ -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) - } -} diff --git a/shortdrama/model/adapter/chat_openai.go b/shortdrama/model/adapter/chat_openai.go deleted file mode 100644 index f61b323..0000000 --- a/shortdrama/model/adapter/chat_openai.go +++ /dev/null @@ -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 -} diff --git a/shortdrama/model/adapter/chat_qwen.go b/shortdrama/model/adapter/chat_qwen.go deleted file mode 100644 index 2b4b730..0000000 --- a/shortdrama/model/adapter/chat_qwen.go +++ /dev/null @@ -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) -} diff --git a/shortdrama/model/adapter/video.go b/shortdrama/model/adapter/video.go index 351cc51..743c8d6 100644 --- a/shortdrama/model/adapter/video.go +++ b/shortdrama/model/adapter/video.go @@ -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) } diff --git a/shortdrama/model/dto/config_dto.go b/shortdrama/model/dto/config_dto.go index 0cadc30..ddc8551 100644 --- a/shortdrama/model/dto/config_dto.go +++ b/shortdrama/model/dto/config_dto.go @@ -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:"视频生成任务回调地址"` } diff --git a/shortdrama/model/entity/model_config.go b/shortdrama/model/entity/model_config.go index b2929fd..7c871ec 100644 --- a/shortdrama/model/entity/model_config.go +++ b/shortdrama/model/entity/model_config.go @@ -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:"更新时间"` } diff --git a/shortdrama/service/config_service.go b/shortdrama/service/config_service.go index 4d65e85..bc0a3b8 100644 --- a/shortdrama/service/config_service.go +++ b/shortdrama/service/config_service.go @@ -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"): diff --git a/shortdrama/service/drama_service.go b/shortdrama/service/drama_service.go index 6f5310b..364023d 100644 --- a/shortdrama/service/drama_service.go +++ b/shortdrama/service/drama_service.go @@ -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 提交视频合成任务(使用视频适配器) diff --git a/shortdrama/service/episode_service.go b/shortdrama/service/episode_service.go index 292462e..596d063 100644 --- a/shortdrama/service/episode_service.go +++ b/shortdrama/service/episode_service.go @@ -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