feat: 引入 Eino 框架,实现四阶段生成管线

基于 compose.Graph 编排 PromptBuilder → AssetGenerator → QualitySupervisor → FormatAdapter,
含质检重试分支(最多 3 次)和降级输出。推理层提供 mock 模式,可替换为真实 API。
This commit is contained in:
2026-05-24 13:21:11 +08:00
parent 2601af6da6
commit 9be4b16bdc
7 changed files with 696 additions and 1 deletions
+19 -1
View File
@@ -2,13 +2,20 @@ module gen2d
go 1.26.3
require github.com/gin-gonic/gin v1.12.0
require (
github.com/cloudwego/eino v0.8.13
github.com/gin-gonic/gin v1.12.0
)
require (
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/buger/jsonparser v1.1.1 // indirect
github.com/bytedance/gopkg v0.1.3 // indirect
github.com/bytedance/sonic v1.15.0 // indirect
github.com/bytedance/sonic/loader v0.5.0 // indirect
github.com/cloudwego/base64x v0.1.6 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/eino-contrib/jsonschema v1.0.3 // indirect
github.com/gabriel-vasile/mimetype v1.4.12 // indirect
github.com/gin-contrib/sse v1.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
@@ -16,22 +23,33 @@ require (
github.com/go-playground/validator/v10 v10.30.1 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/goccy/go-yaml v1.19.2 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/goph/emperror v0.17.2 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/nikolalohinski/gonja v1.5.3 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/quic-go/qpack v0.6.0 // indirect
github.com/quic-go/quic-go v0.59.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/slongfield/pyfmt v0.0.0-20220222012616-ea85ff4c361f // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.3.1 // indirect
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
github.com/yargevad/filepathx v1.0.0 // indirect
go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect
golang.org/x/arch v0.22.0 // indirect
golang.org/x/crypto v0.48.0 // indirect
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 // indirect
golang.org/x/net v0.51.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/text v0.34.0 // indirect
google.golang.org/protobuf v1.36.10 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
+92
View File
@@ -1,20 +1,40 @@
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.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
github.com/buger/jsonparser v1.1.1/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.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE=
github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k=
github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE=
github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
github.com/certifi/gocertifi v0.0.0-20190105021004-abcd57078448/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4=
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
github.com/cloudwego/eino v0.8.13 h1:z5dhaZNN8TWZbP/lgKxGmF26Ii8fPeUlQCGV/NTtms0=
github.com/cloudwego/eino v0.8.13/go.mod h1:+2N4nsMPxA6kGBHpH+75JuTfEcGprAMTdsZESrShKpU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/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/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw=
github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8=
github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc=
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-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
@@ -27,35 +47,85 @@ github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
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/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/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
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/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
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 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/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/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.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw=
github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
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.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
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.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY=
github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec=
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.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
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=
@@ -66,24 +136,46 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY=
github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
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.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE=
go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
golang.org/x/arch v0.22.0 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI=
golang.org/x/arch v0.22.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 h1:MGwJjxBy0HJshjDNfLsYO8xppfqWlA5ZT9OhtUUhTNw=
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
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.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
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=
+102
View File
@@ -0,0 +1,102 @@
package service
import (
"context"
"crypto/rand"
"fmt"
"image"
"image/color"
"image/png"
"bytes"
)
// GenerateImages 调用 AI 推理 API 生成图片。
// MVP 阶段返回 mock 占位图。
func GenerateImages(ctx context.Context, prompt string, params AssetParams) ([]GeneratedImage, error) {
size := params.Resolution
if size <= 0 {
size = 64
}
count := 1
if params.Frames.Directions > 0 && params.Frames.FramesPerDirection > 0 {
count = params.Frames.Directions * params.Frames.FramesPerDirection
}
images := make([]GeneratedImage, count)
for i := 0; i < count; i++ {
data, err := generateMockImage(size, i)
if err != nil {
return nil, fmt.Errorf("generate mock image %d: %w", i, err)
}
images[i] = GeneratedImage{
Data: data,
Width: size,
Height: size,
Format: "png",
}
}
return images, nil
}
// QualityChecker 质检函数,可替换用于测试。
// 签名:(ctx, images, style) → (pass, reason, error)
var QualityChecker = defaultCheckQuality
// CheckQuality 调用当前 QualityChecker。
func CheckQuality(ctx context.Context, images []GeneratedImage, style map[string]string) (bool, string, error) {
return QualityChecker(ctx, images, style)
}
// defaultCheckQuality 默认 mock 质检,始终返回 pass。
func defaultCheckQuality(ctx context.Context, images []GeneratedImage, style map[string]string) (bool, string, error) {
return true, "", nil
}
// NewCountedQualityChecker 创建一个在第 passOnRetry 次调用时返回 pass 的质检函数。
func NewCountedQualityChecker(passOnRetry int) func(context.Context, []GeneratedImage, map[string]string) (bool, string, error) {
var callCount int
return func(_ context.Context, _ []GeneratedImage, _ map[string]string) (bool, string, error) {
callCount++
if callCount >= passOnRetry {
return true, "", nil
}
return false, fmt.Sprintf("风格不一致(第 %d 次质检)", callCount), nil
}
}
// AlwaysFailQualityChecker 始终返回 fail 的质检函数。
func AlwaysFailQualityChecker() func(context.Context, []GeneratedImage, map[string]string) (bool, string, error) {
return func(_ context.Context, _ []GeneratedImage, _ map[string]string) (bool, string, error) {
return false, "风格不一致", nil
}
}
// generateMockImage 生成一张带随机色块的 PNG 占位图
func generateMockImage(size int, seed int) ([]byte, error) {
img := image.NewRGBA(image.Rect(0, 0, size, size))
// 用 seed 生成不同颜色
r := uint8((seed*47 + 13) % 256)
g := uint8((seed*83 + 37) % 256)
b := uint8((seed*61 + 71) % 256)
for y := 0; y < size; y++ {
for x := 0; x < size; x++ {
img.Set(x, y, color.RGBA{R: r, G: g, B: b, A: 255})
}
}
var buf bytes.Buffer
if err := png.Encode(&buf, img); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
// generateRandomBytes 用于生成随机数据(备用)
func generateRandomBytes(n int) ([]byte, error) {
b := make([]byte, n)
_, err := rand.Read(b)
return b, err
}
+188
View File
@@ -0,0 +1,188 @@
package service
import (
"context"
"fmt"
"strings"
"github.com/cloudwego/eino/compose"
)
// PromptBuilder 节点:接收输入,输出三段式提示词
var promptBuilderNode = compose.InvokableLambda(func(ctx context.Context, in PipelineInput) (string, error) {
return buildPrompt(in), nil
})
// promptBuilderPreHandler 首次运行时保存输入到 state;重试时注入 RejectReason
func promptBuilderPreHandler(ctx context.Context, in PipelineInput, state *PipelineState) (PipelineInput, error) {
if state.RetryCount == 0 {
state.Input = in
} else if state.RejectReason != "" {
in.RejectReason = state.RejectReason
}
return in, nil
}
// promptBuilderPostHandler 将提示词写入全局状态
func promptBuilderPostHandler(ctx context.Context, out string, state *PipelineState) (string, error) {
state.FinalPrompt = out
return out, nil
}
// AssetGenerator 节点:调用 AI 推理 API 出图
var assetGeneratorNode = compose.InvokableLambda(func(ctx context.Context, prompt string) ([]GeneratedImage, error) {
var params AssetParams
_ = compose.ProcessState[*PipelineState](ctx, func(_ context.Context, state *PipelineState) error {
params = state.Input.Params
return nil
})
return GenerateImages(ctx, prompt, params)
})
// assetGeneratorPostHandler 将原始图片写入全局状态
func assetGeneratorPostHandler(ctx context.Context, out []GeneratedImage, state *PipelineState) ([]GeneratedImage, error) {
state.RawImages = out
return out, nil
}
// QualitySupervisor 节点:质检,输出 PipelineInput 供下游节点消费。
// 将图片存入 state,设置路由目标 NextNode。
var qualitySupervisorNode = compose.InvokableLambda(func(ctx context.Context, images []GeneratedImage) (PipelineInput, error) {
var input PipelineInput
err := compose.ProcessState[*PipelineState](ctx, func(_ context.Context, state *PipelineState) error {
// 保存图片到状态
state.RawImages = images
// 质检
style := mergeStyle(state.Input.ProjectStyle, state.Input.TaskStyle)
pass, reason, checkErr := CheckQuality(ctx, images, style)
if checkErr != nil {
return fmt.Errorf("quality check: %w", checkErr)
}
state.PassQuality = pass
if !pass {
state.RejectReason = reason
}
// 决定路由
if pass {
state.NextNode = nodeFormatAdapter
} else if state.RetryCount >= 3 {
state.NextNode = nodeFormatAdapter // 超过重试次数,降级输出
} else {
state.RetryCount++
state.NextNode = nodePromptBuilder // 重生成
}
input = state.Input
return nil
})
if err != nil {
return PipelineInput{}, err
}
return input, nil
})
// formatAdapterNode 节点:从 state 读取图片,格式转换,组装输出
var formatAdapterNode = compose.InvokableLambda(func(ctx context.Context, input PipelineInput) (PipelineOutput, error) {
var images []GeneratedImage
_ = compose.ProcessState[*PipelineState](ctx, func(_ context.Context, state *PipelineState) error {
images = state.RawImages
return nil
})
assets := make([]Asset, len(images))
for i, img := range images {
assets[i] = Asset{
Data: img.Data,
Format: img.Format,
URL: fmt.Sprintf("output/%d.%s", i, img.Format),
}
}
resolution := input.Params.Resolution
if resolution <= 0 {
resolution = 64
}
metadata := AssetMetadata{
FrameWidth: resolution,
FrameHeight: resolution,
FrameCount: len(images),
Directions: input.Params.Frames.Directions,
}
return PipelineOutput{
Assets: assets,
Metadata: metadata,
}, nil
})
// buildPrompt 构建三段式提示词
func buildPrompt(in PipelineInput) string {
var parts []string
// 【主题】
parts = append(parts, fmt.Sprintf("【主题】%s", in.Prompt))
// 【约束】
constraints := buildConstraints(in)
parts = append(parts, fmt.Sprintf("【约束】%s", constraints))
// 【内容】
content := buildContent(in)
parts = append(parts, fmt.Sprintf("【内容】%s", content))
return strings.Join(parts, "\n")
}
// buildConstraints 合并风格 + 负面提示词 + 重试原因
func buildConstraints(in PipelineInput) string {
style := mergeStyle(in.ProjectStyle, in.TaskStyle)
var parts []string
for k, v := range style {
parts = append(parts, fmt.Sprintf("%s: %s", k, v))
}
if in.RejectReason != "" {
parts = append(parts, fmt.Sprintf("上次质检问题:%s", in.RejectReason))
}
if len(parts) == 0 {
return "无特殊约束"
}
return strings.Join(parts, "; ")
}
// buildContent 构建技术参数段
func buildContent(in PipelineInput) string {
var parts []string
parts = append(parts, fmt.Sprintf("素材类型: %s", in.AssetType))
if in.Params.Resolution > 0 {
parts = append(parts, fmt.Sprintf("分辨率: %d", in.Params.Resolution))
}
if in.Params.Frames.Directions > 0 {
parts = append(parts, fmt.Sprintf("方向数: %d", in.Params.Frames.Directions))
}
if in.Params.Frames.FramesPerDirection > 0 {
parts = append(parts, fmt.Sprintf("每方向帧数: %d", in.Params.Frames.FramesPerDirection))
}
if in.Params.Format != "" {
parts = append(parts, fmt.Sprintf("输出格式: %s", in.Params.Format))
}
return strings.Join(parts, "; ")
}
// mergeStyle 合并工程风格与任务风格覆盖,任务同名键覆盖工程
func mergeStyle(projectStyle, taskStyle map[string]string) map[string]string {
result := make(map[string]string)
for k, v := range projectStyle {
result[k] = v
}
for k, v := range taskStyle {
result[k] = v
}
return result
}
+102
View File
@@ -0,0 +1,102 @@
package service
import (
"context"
"fmt"
"github.com/cloudwego/eino/compose"
)
const (
nodePromptBuilder = "prompt_builder"
nodeAssetGenerator = "asset_generator"
nodeQualitySupervisor = "quality_supervisor"
nodeFormatAdapter = "format_adapter"
)
// NewGenerateGraph 创建四阶段生成管线 Graph。
//
// START → PromptBuilder → AssetGenerator → QualitySupervisor
// ├── pass → FormatAdapter → END
// └── fail, retry<3 → PromptBuilder
// └── fail, retry>=3 → FormatAdapter (降级)
func NewGenerateGraph() (*compose.Graph[PipelineInput, PipelineOutput], error) {
g := compose.NewGraph[PipelineInput, PipelineOutput](
compose.WithGenLocalState(func(ctx context.Context) *PipelineState {
return &PipelineState{}
}),
)
// 添加节点
if err := g.AddLambdaNode(nodePromptBuilder, promptBuilderNode,
compose.WithStatePreHandler(promptBuilderPreHandler),
compose.WithStatePostHandler(promptBuilderPostHandler),
); err != nil {
return nil, fmt.Errorf("add %s node: %w", nodePromptBuilder, err)
}
if err := g.AddLambdaNode(nodeAssetGenerator, assetGeneratorNode,
compose.WithStatePostHandler(assetGeneratorPostHandler),
); err != nil {
return nil, fmt.Errorf("add %s node: %w", nodeAssetGenerator, err)
}
if err := g.AddLambdaNode(nodeQualitySupervisor, qualitySupervisorNode); err != nil {
return nil, fmt.Errorf("add %s node: %w", nodeQualitySupervisor, err)
}
if err := g.AddLambdaNode(nodeFormatAdapter, formatAdapterNode); err != nil {
return nil, fmt.Errorf("add %s node: %w", nodeFormatAdapter, err)
}
// 连线:正常路径
if err := g.AddEdge(compose.START, nodePromptBuilder); err != nil {
return nil, fmt.Errorf("add edge START->%s: %w", nodePromptBuilder, err)
}
if err := g.AddEdge(nodePromptBuilder, nodeAssetGenerator); err != nil {
return nil, fmt.Errorf("add edge %s->%s: %w", nodePromptBuilder, nodeAssetGenerator, err)
}
if err := g.AddEdge(nodeAssetGenerator, nodeQualitySupervisor); err != nil {
return nil, fmt.Errorf("add edge %s->%s: %w", nodeAssetGenerator, nodeQualitySupervisor, err)
}
if err := g.AddEdge(nodeFormatAdapter, compose.END); err != nil {
return nil, fmt.Errorf("add edge %s->END: %w", nodeFormatAdapter, err)
}
// 连线:质检分支(从 state.NextNode 读取路由目标)
if err := g.AddBranch(nodeQualitySupervisor, compose.NewGraphBranch(
func(ctx context.Context, _ PipelineInput) (string, error) {
var next string
_ = compose.ProcessState[*PipelineState](ctx, func(_ context.Context, state *PipelineState) error {
next = state.NextNode
return nil
})
return next, nil
},
map[string]bool{nodePromptBuilder: true, nodeFormatAdapter: true},
)); err != nil {
return nil, fmt.Errorf("add branch at %s: %w", nodeQualitySupervisor, err)
}
return g, nil
}
// RunPipeline 编译并执行生成管线
func RunPipeline(ctx context.Context, in PipelineInput) (*PipelineOutput, error) {
g, err := NewGenerateGraph()
if err != nil {
return nil, fmt.Errorf("create graph: %w", err)
}
r, err := g.Compile(ctx, compose.WithMaxRunSteps(20))
if err != nil {
return nil, fmt.Errorf("compile graph: %w", err)
}
output, err := r.Invoke(ctx, in)
if err != nil {
return nil, fmt.Errorf("invoke pipeline: %w", err)
}
return &output, nil
}
+129
View File
@@ -0,0 +1,129 @@
package service
import (
"context"
"testing"
)
func TestPipeline_HappyPath(t *testing.T) {
// 质检一次通过
QualityChecker = func(_ context.Context, _ []GeneratedImage, _ map[string]string) (bool, string, error) {
return true, "", nil
}
defer func() { QualityChecker = defaultCheckQuality }()
output, err := RunPipeline(context.Background(), PipelineInput{
Prompt: "一个拿剑的小人",
AssetType: "sprite",
ProjectStyle: map[string]string{
"artStyle": "pixel",
"palette": "warm",
},
Params: AssetParams{
Resolution: 64,
Format: "spritesheet",
},
})
if err != nil {
t.Fatalf("RunPipeline failed: %v", err)
}
if len(output.Assets) == 0 {
t.Fatal("expected non-empty assets")
}
if output.Metadata.FrameWidth != 64 {
t.Errorf("expected FrameWidth=64, got %d", output.Metadata.FrameWidth)
}
if output.Metadata.FrameHeight != 64 {
t.Errorf("expected FrameHeight=64, got %d", output.Metadata.FrameHeight)
}
}
func TestPipeline_RetryThenPass(t *testing.T) {
// 质检前 2 次 fail,第 3 次 pass
QualityChecker = NewCountedQualityChecker(3)
defer func() { QualityChecker = defaultCheckQuality }()
output, err := RunPipeline(context.Background(), PipelineInput{
Prompt: "一把火焰剑",
AssetType: "sprite",
Params: AssetParams{
Resolution: 32,
Frames: FrameParams{
Directions: 4,
FramesPerDirection: 2,
},
Format: "spritesheet",
},
})
if err != nil {
t.Fatalf("RunPipeline failed: %v", err)
}
// 4 directions × 2 frames = 8 张图
if len(output.Assets) != 8 {
t.Errorf("expected 8 assets, got %d", len(output.Assets))
}
if output.Metadata.FrameCount != 8 {
t.Errorf("expected FrameCount=8, got %d", output.Metadata.FrameCount)
}
if output.Metadata.Directions != 4 {
t.Errorf("expected Directions=4, got %d", output.Metadata.Directions)
}
}
func TestPipeline_MaxRetryDegrade(t *testing.T) {
// 质检始终 fail,超过 3 次后降级输出
QualityChecker = AlwaysFailQualityChecker()
defer func() { QualityChecker = defaultCheckQuality }()
output, err := RunPipeline(context.Background(), PipelineInput{
Prompt: "一只飞龙",
AssetType: "sprite",
Params: AssetParams{
Resolution: 48,
},
})
if err != nil {
t.Fatalf("RunPipeline failed: %v", err)
}
// 降级也应该有输出
if len(output.Assets) == 0 {
t.Fatal("expected non-empty assets even on degrade")
}
if output.Metadata.FrameWidth != 48 {
t.Errorf("expected FrameWidth=48, got %d", output.Metadata.FrameWidth)
}
}
func TestPipeline_StyleMerge(t *testing.T) {
// 验证风格合并:task 覆盖 project
QualityChecker = func(_ context.Context, _ []GeneratedImage, style map[string]string) (bool, string, error) {
// 验证合并结果
if style["artStyle"] != "realistic" {
t.Errorf("expected artStyle=realistic (task override), got %s", style["artStyle"])
}
if style["palette"] != "warm" {
t.Errorf("expected palette=warm (from project), got %s", style["palette"])
}
return true, "", nil
}
defer func() { QualityChecker = defaultCheckQuality }()
_, err := RunPipeline(context.Background(), PipelineInput{
Prompt: "测试风格合并",
AssetType: "sprite",
ProjectStyle: map[string]string{
"artStyle": "pixel",
"palette": "warm",
},
TaskStyle: map[string]string{
"artStyle": "realistic", // 覆盖 project
},
Params: AssetParams{Resolution: 64},
})
if err != nil {
t.Fatalf("RunPipeline failed: %v", err)
}
}
+64
View File
@@ -0,0 +1,64 @@
package service
// PipelineInput 管线入口输入
type PipelineInput struct {
Prompt string // 用户原始文本
AssetType string // 素材类型:sprite / background / ui / animation
ProjectStyle map[string]string // 工程风格键值对
TaskStyle map[string]string // 任务风格覆盖
Params AssetParams // 技术参数
RejectReason string // 重试时由 state 注入
}
// PipelineState Graph 全局状态,通过 WithGenLocalState 注入
type PipelineState struct {
Input PipelineInput
FinalPrompt string // PromptBuilder 输出的三段式提示词
RawImages []GeneratedImage // AssetGenerator 输出的原始图片
PassQuality bool // QualitySupervisor 质检结果
RejectReason string // 质检不通过原因
RetryCount int // 重试次数
NextNode string // QualitySupervisor 设置的路由目标
}
// PipelineOutput 管线最终输出
type PipelineOutput struct {
Assets []Asset // 生成结果素材列表
Metadata AssetMetadata // 元数据
}
// AssetParams 技术参数
type AssetParams struct {
Resolution int
Frames FrameParams
Format string // "spritesheet" / "individual"
}
// FrameParams 帧参数
type FrameParams struct {
Directions int
FramesPerDirection int
}
// GeneratedImage AI 生成的原始图片
type GeneratedImage struct {
Data []byte
Width int
Height int
Format string
}
// Asset 最终输出素材
type Asset struct {
Data []byte
Format string
URL string
}
// AssetMetadata 素材元数据
type AssetMetadata struct {
FrameWidth int
FrameHeight int
FrameCount int
Directions int
}