├── docs ├── user_guidance.md ├── intro.md └── development.md ├── internal ├── dao │ ├── .gitkeep │ ├── mongodb │ │ ├── init.go │ │ ├── mnew.go │ │ ├── chat.go │ │ └── code_generate.go │ ├── p_code_gen_retrieval_snippet_map.go │ ├── p_chat_records.go │ ├── p_code_gen_records.go │ └── internal │ │ ├── p_code_gen_retrieval_snippet_map.go │ │ ├── p_chat_records.go │ │ └── p_code_gen_records.go ├── model │ ├── .gitkeep │ ├── do │ │ ├── .gitkeep │ │ ├── p_code_gen_retrieval_snippet_map.go │ │ ├── p_code_gen_records.go │ │ └── p_chat_records.go │ ├── entity │ │ ├── .gitkeep │ │ ├── p_code_gen_retrieval_snippet_map.go │ │ ├── p_chat_records.go │ │ └── p_code_gen_records.go │ └── input │ │ ├── commentin │ │ └── comment.go │ │ ├── optimizein │ │ └── optimize.go │ │ ├── explainin │ │ └── explain.go │ │ ├── unit_testin │ │ └── unit.go │ │ ├── editin │ │ └── edit.go │ │ └── chatin │ │ └── chat.go ├── logic │ ├── middleware │ │ ├── recovery.go │ │ ├── init.go │ │ └── error_response.go │ ├── logic.go │ ├── common │ │ ├── chat_common_test.go │ │ └── code_generate_common.go │ ├── chat │ │ └── chat.go │ ├── code │ │ └── code_test.go │ ├── comment │ │ └── comment.go │ ├── explain │ │ └── explain.go │ ├── edit │ │ └── edit.go │ ├── optimize │ │ └── optimize.go │ └── unit_test │ │ └── unit.go ├── router │ ├── hello.go │ ├── chat.go │ ├── code.go │ ├── edit.go │ ├── comment.go │ ├── explain.go │ ├── unit.go │ └── optimize.go ├── consts │ ├── code.go │ └── model.go ├── service │ ├── edit.go │ ├── unit.go │ ├── comment.go │ ├── explain.go │ ├── middleware.go │ ├── optimize.go │ ├── chat.go │ └── code.go ├── controller │ ├── hello │ │ └── hello_v1.go │ ├── utils │ │ ├── ccode │ │ │ └── ccode.go │ │ ├── cerror │ │ │ └── cerror.go │ │ ├── response │ │ │ └── response.go │ │ └── app_utils.go │ ├── code │ │ └── code_v1.go │ ├── chat │ │ └── chat_v1.go │ ├── edit │ │ └── edit_v1.go │ ├── comment │ │ └── comment_v1.go │ ├── explain │ │ └── explain_v1.go │ ├── unit_test │ │ ├── unit_v1.go │ │ └── unit_v1_test.go │ └── optimize │ │ └── optimize_v1.go └── cmd │ └── cmd.go ├── manifest ├── i18n │ └── .gitkeep ├── deploy │ ├── initdb.d │ │ └── init-mongo.sh │ └── docker-compose │ │ └── docker-compose.yml └── config │ ├── xcoder_code_generate_retrieval_cfc_params_conf.json │ ├── xcoder_chat_llm_params_conf.json │ ├── config.yaml │ └── xcoder_code_generate_llm_params_conf.json ├── .gitattributes ├── core ├── retrieval │ ├── keywords │ │ ├── python.txt │ │ ├── golang.txt │ │ ├── java.txt │ │ └── javascript.txt │ └── retrieval_test.go ├── prompts │ ├── code │ │ ├── version.go │ │ └── code.go │ ├── chat │ │ ├── version.go │ │ └── chat.go │ ├── edit │ │ ├── version.go │ │ └── edit.go │ ├── unit_test │ │ ├── version.go │ │ └── unit.go │ ├── comment │ │ ├── version.go │ │ └── comment.go │ ├── explain │ │ ├── version.go │ │ └── explain.go │ └── optimize │ │ ├── version.go │ │ └── optimize.go ├── callbacks │ ├── callbacks.go │ └── handler.go ├── llms │ ├── vllm │ │ └── xcoder │ │ │ ├── xcoderllm_options.go │ │ │ ├── internal │ │ │ └── xcoderclient │ │ │ │ ├── xcoderclient.go │ │ │ │ └── code.go │ │ │ └── xcoderllm.go │ ├── base.go │ ├── openai │ │ ├── llm.go │ │ ├── openaillm_option.go │ │ └── internal │ │ │ └── openaiclient │ │ │ └── openaiclient.go │ └── count_token.go └── schema │ └── schema.go ├── Makefile ├── api ├── common │ └── response.go ├── edit │ └── v1 │ │ └── edit.go ├── explain │ └── v1 │ │ └── explain.go ├── optimize │ └── v1 │ │ └── optimize.go ├── code │ └── v1 │ │ └── code.go ├── unit_test │ └── v1 │ │ └── unit.go ├── comment │ └── v1 │ │ └── comment.go ├── chat │ └── v1 │ │ └── chat.go └── hello │ └── v1 │ └── hello.go ├── .gitignore ├── utility ├── xmongo │ ├── config.go │ └── mongodb.go ├── xapollo │ ├── viper_new.go │ ├── chat.go │ ├── retrieval.go │ └── code.go ├── xruntime │ └── xruntime.go ├── xcontext │ └── xcontext.go ├── xconcurrent │ └── compute.go └── xutils │ └── xutils.go ├── hack ├── hack-cli.mk ├── config.yaml └── hack.mk ├── llm_deploy ├── Dockerfile ├── docker-compose.codellama.yaml └── docker-compose.deepseker.yaml ├── main.go ├── Dockerfile ├── README.MD └── go.mod /docs/user_guidance.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /internal/dao/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /internal/model/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /manifest/i18n/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /internal/model/do/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /internal/model/entity/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * linguist-language=GO -------------------------------------------------------------------------------- /core/retrieval/keywords/python.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /core/prompts/code/version.go: -------------------------------------------------------------------------------- 1 | package code 2 | -------------------------------------------------------------------------------- /core/prompts/chat/version.go: -------------------------------------------------------------------------------- 1 | package chat 2 | 3 | const PromptVersion = "chat_v1.0.0" 4 | -------------------------------------------------------------------------------- /core/prompts/edit/version.go: -------------------------------------------------------------------------------- 1 | package edit 2 | 3 | const PromptVersion = `edit_v1.0.0` 4 | -------------------------------------------------------------------------------- /core/prompts/unit_test/version.go: -------------------------------------------------------------------------------- 1 | package unit_test 2 | 3 | const PromptVersion = "ut_v1.0.0" 4 | -------------------------------------------------------------------------------- /core/prompts/comment/version.go: -------------------------------------------------------------------------------- 1 | package comment 2 | 3 | const PromptVersion = `comment_v1.0.0` 4 | -------------------------------------------------------------------------------- /core/prompts/explain/version.go: -------------------------------------------------------------------------------- 1 | package explain 2 | 3 | const PromptVersion = `explain_v1.0.0` 4 | -------------------------------------------------------------------------------- /core/prompts/optimize/version.go: -------------------------------------------------------------------------------- 1 | package optimize 2 | 3 | const PromptVersion = `optimize_v1.0.0` 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ROOT_DIR = $(shell pwd) 2 | NAMESPACE = "default" 3 | DEPLOY_NAME = "template-single" 4 | DOCKER_NAME = "template-single" 5 | 6 | include ./hack/hack.mk -------------------------------------------------------------------------------- /api/common/response.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | type Response struct { 4 | Code int `json:"code"` 5 | Message string `json:"message"` 6 | Data interface{} `json:"data"` 7 | } 8 | -------------------------------------------------------------------------------- /internal/logic/middleware/recovery.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "github.com/gogf/gf/v2/net/ghttp" 5 | ) 6 | 7 | func (s *sMiddleware) RecoveryHandler(r *ghttp.Request) { 8 | r.Middleware.Next() 9 | } 10 | -------------------------------------------------------------------------------- /core/retrieval/keywords/golang.txt: -------------------------------------------------------------------------------- 1 | break 2 | default 3 | func 4 | interface 5 | select 6 | case 7 | defer 8 | go 9 | map 10 | struct 11 | chan 12 | else 13 | goto 14 | package 15 | switch 16 | const 17 | fallthrough 18 | if 19 | range 20 | type 21 | continue 22 | for 23 | import 24 | return 25 | var -------------------------------------------------------------------------------- /core/retrieval/keywords/java.txt: -------------------------------------------------------------------------------- 1 | abstract 2 | assert 3 | boolean 4 | break 5 | byte 6 | case 7 | catch 8 | char 9 | class 10 | const 11 | continue 12 | default 13 | do 14 | double 15 | else 16 | enum 17 | extends 18 | final 19 | finally 20 | float 21 | for 22 | goto 23 | if 24 | implements 25 | import -------------------------------------------------------------------------------- /internal/logic/middleware/init.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import "xcoder/internal/service" 4 | 5 | type sMiddleware struct{} 6 | 7 | func NewMiddleware() service.IMiddleware { 8 | return &sMiddleware{} 9 | } 10 | 11 | func init() { 12 | service.RegisterMiddleware(NewMiddleware()) 13 | } 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .buildpath 2 | .hgignore.swp 3 | .project 4 | .orig 5 | .swp 6 | .idea/ 7 | .settings/ 8 | .vscode/ 9 | bin/ 10 | **/.DS_Store 11 | gf 12 | main 13 | main.exe 14 | output/ 15 | manifest/output/ 16 | temp/ 17 | temp.yaml 18 | bin 19 | 20 | # env 21 | .env 22 | .env.* 23 | 24 | # data 25 | data/ -------------------------------------------------------------------------------- /core/retrieval/keywords/javascript.txt: -------------------------------------------------------------------------------- 1 | abstract 2 | arguments 3 | await 4 | boolean 5 | break 6 | byte 7 | case 8 | catch 9 | char 10 | class 11 | const 12 | continue 13 | debugger 14 | default 15 | delete 16 | do 17 | double 18 | else 19 | enum 20 | eval 21 | export 22 | extends 23 | false 24 | final 25 | finally 26 | float -------------------------------------------------------------------------------- /internal/router/hello.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "context" 5 | "github.com/gogf/gf/v2/net/ghttp" 6 | "xcoder/internal/controller/hello" 7 | ) 8 | 9 | func Hs(ctx context.Context, group *ghttp.RouterGroup) { 10 | group.Group("/", func(group *ghttp.RouterGroup) { 11 | group.Bind(hello.NewV1()) 12 | }) 13 | } 14 | -------------------------------------------------------------------------------- /manifest/deploy/initdb.d/init-mongo.sh: -------------------------------------------------------------------------------- 1 | # set -e 2 | 3 | mongosh < /dev/null 2>&1 || if [[ "$?" -ne "0" ]]; then \ 17 | echo "GoFame CLI is not installed, start proceeding auto installation..."; \ 18 | make cli; \ 19 | fi; -------------------------------------------------------------------------------- /utility/xruntime/xruntime.go: -------------------------------------------------------------------------------- 1 | package xruntime 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "runtime" 7 | ) 8 | 9 | func MStack(deep, skip int) string { 10 | pc := make([]uintptr, deep+1) // at least 1 entry needed 11 | n := runtime.Callers(skip, pc) 12 | frames := runtime.CallersFrames(pc[:n]) 13 | mstack := bytes.NewBufferString("") 14 | for { 15 | frame, more := frames.Next() 16 | mstack.WriteString(fmt.Sprintf("%s:%d;%s;", frame.File, frame.Line, frame.Function)) 17 | if !more { 18 | break 19 | } 20 | } 21 | return mstack.String() 22 | } 23 | -------------------------------------------------------------------------------- /internal/service/edit.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | "xcoder/internal/model/input/chatin" 6 | "xcoder/internal/model/input/editin" 7 | ) 8 | 9 | type IEdit interface { 10 | SseGenerate(ctx context.Context, in *editin.EditSseGenerateRequest, out chan *chatin.ChatResult) error 11 | } 12 | 13 | var localEdit IEdit 14 | 15 | func Edit() IEdit { 16 | if localEdit == nil { 17 | panic("implement not found for interface IEdit, forgot register?") 18 | } 19 | return localEdit 20 | } 21 | 22 | func RegisterEdit(i IEdit) { 23 | localEdit = i 24 | } 25 | -------------------------------------------------------------------------------- /internal/service/unit.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | "xcoder/internal/model/input/chatin" 6 | "xcoder/internal/model/input/unit_testin" 7 | ) 8 | 9 | type IUnit interface { 10 | SseGenerate(ctx context.Context, in *unit_testin.UnitTestSseGenerateReq, out chan *chatin.ChatResult) error 11 | } 12 | 13 | var localUnit IUnit 14 | 15 | func Unit() IUnit { 16 | if localUnit == nil { 17 | panic("implement not found for interface IUnit, forgot register?") 18 | } 19 | return localUnit 20 | } 21 | 22 | func RegisterUnit(i IUnit) { 23 | localUnit = i 24 | } 25 | -------------------------------------------------------------------------------- /llm_deploy/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nvidia/cuda:12.2.0-devel-ubuntu20.04 2 | ENV DEBIAN_FRONTEND=noninteractive 3 | 4 | RUN sed -i s@/archive.ubuntu.com/@/mirrors.aliyun.com/@g /etc/apt/sources.list && \ 5 | sed -i s@/security.ubuntu.com/@/mirrors.aliyun.com/@g /etc/apt/sources.list && \ 6 | apt-get clean 7 | 8 | RUN apt-get update -y && apt-get install -y python3.9 python3.9-distutils curl git vim 9 | RUN curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py 10 | RUN python3.9 get-pip.py 11 | RUN update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.9 1 12 | 13 | RUN pip3 install vllm -------------------------------------------------------------------------------- /internal/service/comment.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | "xcoder/internal/model/input/chatin" 6 | "xcoder/internal/model/input/commentin" 7 | ) 8 | 9 | type IComment interface { 10 | SseGenerate(ctx context.Context, in *commentin.CodeCommentRequest, out chan *chatin.ChatResult) error 11 | } 12 | 13 | var localComment IComment 14 | 15 | func Comment() IComment { 16 | if localComment == nil { 17 | panic("implement not found for interface IComment, forgot register?") 18 | } 19 | return localComment 20 | } 21 | 22 | func RegisterComment(i IComment) { 23 | localComment = i 24 | } 25 | -------------------------------------------------------------------------------- /internal/service/explain.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | "xcoder/internal/model/input/chatin" 6 | "xcoder/internal/model/input/explainin" 7 | ) 8 | 9 | type IExplain interface { 10 | SseGenerate(ctx context.Context, in *explainin.ExplainSseGenerateRequest, out chan *chatin.ChatResult) error 11 | } 12 | 13 | var localExplain IExplain 14 | 15 | func Explain() IExplain { 16 | if localExplain == nil { 17 | panic("implement not found for interface IExplain, forgot register?") 18 | } 19 | return localExplain 20 | } 21 | 22 | func RegisterExplain(i IExplain) { 23 | localExplain = i 24 | } 25 | -------------------------------------------------------------------------------- /internal/controller/hello/hello_v1.go: -------------------------------------------------------------------------------- 1 | package hello 2 | 3 | import ( 4 | "context" 5 | "github.com/gogf/gf/v2/frame/g" 6 | v1 "xcoder/api/hello/v1" 7 | ) 8 | 9 | type ControllerV1 struct{} 10 | 11 | func NewV1() *ControllerV1 { 12 | return &ControllerV1{} 13 | } 14 | 15 | func (c *ControllerV1) Hello(ctx context.Context, req *v1.HelloReq) (res *v1.HelloRes, err error) { 16 | g.RequestFromCtx(ctx).Response.Writeln("Hello World!") 17 | return 18 | } 19 | 20 | func (c *ControllerV1) Hs(ctx context.Context, req *v1.HsReq) (res *v1.HsRes, err error) { 21 | g.RequestFromCtx(ctx).Response.Writeln("ok") 22 | return 23 | } 24 | -------------------------------------------------------------------------------- /internal/service/middleware.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "github.com/gogf/gf/v2/net/ghttp" 5 | ) 6 | 7 | type IMiddleware interface { 8 | // RecoveryHandler 恢复中间件 9 | RecoveryHandler(r *ghttp.Request) 10 | // ErrResponseHandler HTTP响应及错误返回处理中间件 11 | ErrResponseHandler(r *ghttp.Request) 12 | } 13 | 14 | var localMiddleware IMiddleware 15 | 16 | func Middleware() IMiddleware { 17 | if localMiddleware == nil { 18 | panic("implement not found for interface IMiddleware, forgot register?") 19 | } 20 | return localMiddleware 21 | } 22 | 23 | func RegisterMiddleware(i IMiddleware) { 24 | localMiddleware = i 25 | } 26 | -------------------------------------------------------------------------------- /internal/service/optimize.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | "xcoder/internal/model/input/chatin" 6 | "xcoder/internal/model/input/optimizein" 7 | ) 8 | 9 | type IOptimize interface { 10 | SseGenerate(ctx context.Context, in *optimizein.OptimizeSseGenerateRequest, out chan *chatin.ChatResult) error 11 | } 12 | 13 | var localOptimize IOptimize 14 | 15 | func Optimize() IOptimize { 16 | if localOptimize == nil { 17 | panic("implement not found for interface IOptimize, forgot register?") 18 | } 19 | return localOptimize 20 | } 21 | 22 | func RegisterOptimize(i IOptimize) { 23 | localOptimize = i 24 | } 25 | -------------------------------------------------------------------------------- /internal/service/chat.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | "xcoder/internal/model/input/chatin" 6 | ) 7 | 8 | type IChat interface { 9 | SseGenerate(ctx context.Context, in *chatin.ChatSseGenerateReq, out chan *chatin.ChatResult) error 10 | UpdateChatRecordsFields(ctx context.Context, in *chatin.ChatMessageUpdateMysqlReq) (out *chatin.ChatMessageUpdateMysqlRes, err error) 11 | } 12 | 13 | var localChat IChat 14 | 15 | func Chat() IChat { 16 | if localChat == nil { 17 | panic("implement not found for interface IChat, forgot register?") 18 | } 19 | return localChat 20 | } 21 | 22 | func RegisterChat(i IChat) { 23 | localChat = i 24 | } 25 | -------------------------------------------------------------------------------- /manifest/config/config.yaml: -------------------------------------------------------------------------------- 1 | server: 2 | address: ":8081" 3 | openapiPath: "/api.json" 4 | swaggerPath: "/swagger" 5 | 6 | logger: 7 | level : "all" 8 | stdout: true 9 | 10 | database: 11 | default: 12 | link: "mysql:xcoder_mysql_user:xcoder_mysql_pwd@tcp(xcoder_mysql:3306)/xcoder_mysql_db?charset=utf8&parseTime=True&loc=Local" 13 | debug: true 14 | logger: 15 | path: "./log/sql" 16 | level: "all" 17 | stdout: true 18 | 19 | mongodb: 20 | default: 21 | hosts: "xcoder_mongodb:27017" 22 | dbname: "xcoder_mongo_db" 23 | username: "xcoder_mongo_user" 24 | password: "xcoder_mongo_pwd" 25 | authsource: "admin" 26 | replicaSet: "" -------------------------------------------------------------------------------- /internal/service/code.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | codeV1 "xcoder/api/code/v1" 6 | "xcoder/internal/model/input/codein" 7 | ) 8 | 9 | type ICode interface { 10 | Generate(ctx context.Context, req *codein.CodeGenerateRequest) (res *codeV1.CodeGenerateRes, err error) 11 | UpdateGenerationInfo(ctx context.Context, in *codein.CodeUpdateGenerationInfoReq) (out *codein.CodeUpdateGenerationInfoRes, err error) 12 | } 13 | 14 | var localCode ICode 15 | 16 | func Code() ICode { 17 | if localCode == nil { 18 | panic("implement not found for interface ICode, forgot register?") 19 | } 20 | return localCode 21 | } 22 | 23 | func RegisterCode(i ICode) { 24 | localCode = i 25 | } 26 | -------------------------------------------------------------------------------- /internal/controller/utils/ccode/ccode.go: -------------------------------------------------------------------------------- 1 | package ccode 2 | 3 | var ( 4 | // CodeSuccess 成功,默认 Http Status Code 为 200 5 | CodeSuccess = 100000 6 | CodeSuccessMessage = "success" 7 | 8 | DBModelAlreadyExistsError = 400001 9 | DBModelRecordNotFoundError = 400003 10 | DBModelRecordNotFoundErrorMessage = "record not found" 11 | CodeInternalServerError = 500000 12 | CodeUserOperatorError = 500001 13 | 14 | CodeGenerateCompletionEmpty = 402001 15 | CodeGeneratePromptError = 402002 16 | CodeGeneratePromptErrorMessage = "generate prompt error" 17 | CodeGeneratePredictError = 402003 18 | CodeGeneratePredictErrorMessage = "predict error" 19 | ) 20 | -------------------------------------------------------------------------------- /utility/xcontext/xcontext.go: -------------------------------------------------------------------------------- 1 | package xcontext 2 | 3 | import ( 4 | "context" 5 | "time" 6 | ) 7 | 8 | // WithProtect return a copy of the parent context but if the parent context is cancelled, the returned context will not. 9 | func WithProtect(parent context.Context) (ctx context.Context) { 10 | if parent == nil { 11 | panic("cannot create context from nil parent") 12 | } 13 | return valueOnlyContext{parent} 14 | } 15 | 16 | type valueOnlyContext struct { 17 | context.Context 18 | } 19 | 20 | func (valueOnlyContext) Deadline() (deadline time.Time, ok bool) { 21 | return 22 | } 23 | 24 | func (valueOnlyContext) Done() <-chan struct{} { 25 | return nil 26 | } 27 | 28 | func (valueOnlyContext) Err() error { 29 | return nil 30 | } 31 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | _ "github.com/gogf/gf/contrib/drivers/mysql/v2" 7 | "github.com/gogf/gf/v2/frame/g" 8 | "github.com/gogf/gf/v2/os/gctx" 9 | "xcoder/internal/cmd" 10 | // 初始化 mongodb 11 | _ "xcoder/internal/dao/mongodb" 12 | _ "xcoder/internal/logic" 13 | _ "xcoder/utility/xapollo" 14 | ) 15 | 16 | func main() { 17 | err := connData() 18 | if err != nil { 19 | g.Log().Errorf(context.Background(), "mysql conn error: %v", err) 20 | panic(err) 21 | } 22 | 23 | cmd.Main.Run(gctx.GetInitCtx()) 24 | } 25 | 26 | // 检查 database 连接 27 | func connData() error { 28 | err := g.DB().PingMaster() 29 | if err != nil { 30 | return errors.New("database 连接失败") 31 | } 32 | return nil 33 | } 34 | -------------------------------------------------------------------------------- /internal/dao/mongodb/mnew.go: -------------------------------------------------------------------------------- 1 | package mongodb 2 | 3 | import ( 4 | "context" 5 | "go.mongodb.org/mongo-driver/mongo" 6 | "xcoder/utility/xmongo" 7 | ) 8 | 9 | type MongodbDao struct { 10 | DbName string 11 | mongodb *xmongo.Mongodb 12 | } 13 | 14 | func New() (*MongodbDao, error) { 15 | mongodbConfig, err := xmongo.NewFromFile() 16 | if err != nil { 17 | return nil, err 18 | } 19 | 20 | return &MongodbDao{ 21 | DbName: mongodbConfig.DBName, 22 | mongodb: mongodbConfig, 23 | }, nil 24 | } 25 | 26 | func (d *MongodbDao) GetClient(ctx context.Context, dbName string) (*mongo.Client, error) { 27 | return d.mongodb.GetClient(ctx, dbName) 28 | } 29 | 30 | func (d *MongodbDao) GetDBName(ctx context.Context) string { 31 | return d.mongodb.GetDBName(ctx) 32 | } 33 | 34 | func (d *MongodbDao) Close() { 35 | d.mongodb.Close() 36 | } 37 | -------------------------------------------------------------------------------- /hack/config.yaml: -------------------------------------------------------------------------------- 1 | 2 | # CLI tool, only in development environment. 3 | # https://goframe.org/pages/viewpage.action?pageId=3673173 4 | gfcli: 5 | docker: 6 | build: "-a amd64 -s linux -p temp -ew" 7 | tagPrefixes: 8 | - xcoder/xcoder:latest 9 | 10 | gen: 11 | dao: 12 | - link: "mysql:xcoder_user:xcoder_password@tcp(xcoder_mysql:3306)/xcoder_db?charset=utf8&parseTime=True&loc=Local" # mysql 数据库连接地址 13 | group: "default" # 分组 使用代码生成功能需要填的参数 14 | tables: "" # 指定当前数据库中需要自动生成的数据表。如果为空,表示数据库的所有表都会自动生成。 15 | tablesEx: "" # 指定当前数据库中需要排除自动生成的数据表。 16 | removePrefix: "p_" 17 | descriptionTag: true 18 | noModelComment: true 19 | jsonCase: "CamelLower" 20 | gJsonSupport: true 21 | clear: false -------------------------------------------------------------------------------- /core/llms/vllm/xcoder/internal/xcoderclient/xcoderclient.go: -------------------------------------------------------------------------------- 1 | package xcoderclient 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | ) 7 | 8 | type errorMessage struct { 9 | Error struct { 10 | Message string `json:"message"` 11 | Type string `json:"type"` 12 | } `json:"error"` 13 | } 14 | 15 | var ErrEmptyResponse = errors.New("empty response") 16 | 17 | type Client struct { 18 | baseURL string 19 | Model string 20 | } 21 | 22 | func New(model string, baseURL string) *Client { 23 | return &Client{ 24 | Model: model, 25 | baseURL: baseURL, 26 | } 27 | } 28 | 29 | type CodeGenResponse struct { 30 | Code string `json:"code"` 31 | } 32 | 33 | func (c *Client) CreateCodeGenWithDetail(ctx context.Context, r *CodeGenRequest) (*CodeGenModelResponse, error) { 34 | resp, err := c.createCodeGen(ctx, r) 35 | if err != nil { 36 | return nil, err 37 | } 38 | 39 | return resp, nil 40 | } 41 | -------------------------------------------------------------------------------- /internal/controller/code/code_v1.go: -------------------------------------------------------------------------------- 1 | package code 2 | 3 | import ( 4 | "context" 5 | "github.com/gogf/gf/v2/frame/g" 6 | "github.com/gogf/gf/v2/util/gconv" 7 | codeV1 "xcoder/api/code/v1" 8 | "xcoder/internal/controller/utils/response" 9 | "xcoder/internal/model/input/codein" 10 | "xcoder/internal/service" 11 | ) 12 | 13 | type ControllerV1 struct{} 14 | 15 | func NewV1() *ControllerV1 { 16 | return &ControllerV1{} 17 | } 18 | 19 | func (c *ControllerV1) Generate(ctx context.Context, req *codeV1.CodeGenerateReq) ( 20 | res *codeV1.CodeGenerateRes, err error) { 21 | var ( 22 | in = &codein.CodeGenerateRequest{} 23 | r = g.RequestFromCtx(ctx) 24 | ) 25 | 26 | err = gconv.Struct(req, in) 27 | if err != nil { 28 | return nil, err 29 | } 30 | 31 | generate, err := service.Code().Generate(ctx, in) 32 | if err != nil { 33 | return nil, err 34 | } 35 | 36 | response.JsonSuccess(r, generate) 37 | 38 | return 39 | } 40 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.20-alpine as builder 2 | ENV GOOS=linux 3 | ENV GOPROXY=https://mirrors.aliyun.com/goproxy/,direct 4 | ENV GOSUMDB=off 5 | ENV TZ=Asia/Shanghai 6 | 7 | RUN apk add build-base 8 | 9 | WORKDIR /build/xcoder/ 10 | 11 | COPY go.mod go.sum ./ 12 | RUN go mod download 13 | 14 | COPY . . 15 | 16 | RUN GOOS=linux CGO_ENABLED=1 GOARCH=amd64 go build -ldflags="-s -w" -installsuffix cgo -o /app/main main.go 17 | 18 | FROM alpine 19 | 20 | RUN apk add --no-cache --virtual .build-deps \ 21 | build-base \ 22 | gcc \ 23 | tzdata \ 24 | wget \ 25 | git \ 26 | && apk add --no-cache libstdc++ \ 27 | && apk del .build-deps 28 | 29 | WORKDIR /app 30 | 31 | COPY --from=builder /app/main /app/main 32 | 33 | COPY manifest/config /app/config 34 | 35 | EXPOSE 8081 36 | 37 | RUN echo $'#!/bin/sh\n\ 38 | exec /app/main\n\ 39 | ' > entrypoint.sh 40 | 41 | RUN chmod +x /app/entrypoint.sh 42 | ENTRYPOINT ["/app/entrypoint.sh"] -------------------------------------------------------------------------------- /internal/consts/model.go: -------------------------------------------------------------------------------- 1 | package consts 2 | 3 | type ConversationType string 4 | 5 | const ( 6 | ConversationUT ConversationType = "ut" 7 | ConversationChat ConversationType = "chat" 8 | ConversationEdit ConversationType = "edit" 9 | ConversationExplain ConversationType = "explain" 10 | ConversationComment ConversationType = "comment" 11 | ConversationOptimize ConversationType = "optimize" 12 | ) 13 | 14 | func (s ConversationType) String() string { 15 | return string(s) 16 | } 17 | 18 | const ( 19 | ProjectName = "XCoder" 20 | ProjectVersion = "1.0.0" 21 | ) 22 | 23 | type ContextFileType string 24 | 25 | const ( 26 | ContextUrl ContextFileType = "url" 27 | ContextFile ContextFileType = "file" 28 | ContextFileLocal ContextFileType = "file@local" 29 | ContextFileLocalTest ContextFileType = "file@localTest" 30 | ContextFileOtherTest ContextFileType = "file@otherTest" 31 | ) 32 | 33 | func (s ContextFileType) String() string { 34 | return string(s) 35 | } 36 | -------------------------------------------------------------------------------- /internal/logic/middleware/error_response.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "github.com/gogf/gf/v2/frame/g" 5 | "github.com/gogf/gf/v2/net/ghttp" 6 | "xcoder/internal/controller/utils/cerror" 7 | "xcoder/internal/controller/utils/response" 8 | ) 9 | 10 | func (s *sMiddleware) ErrResponseHandler(r *ghttp.Request) { 11 | r.Middleware.Next() 12 | 13 | ctx := r.Context() 14 | err := r.GetError() 15 | if err == nil { 16 | return 17 | } 18 | 19 | switch cErr := err.(type) { 20 | case *cerror.UserErrorButHttpOk: 21 | g.Log().Warningf(ctx, cErr.Error()) 22 | response.JsonUserErrorButHttpOk(r, cErr) 23 | case *cerror.UserError: 24 | g.Log().Warningf(ctx, cErr.Error()) 25 | response.JsonUserError(r, cErr) 26 | case *cerror.UnauthorizedError: 27 | response.JsonUnauthorizedError(r, cErr) 28 | case *cerror.ServerError: 29 | response.JsonInternalServerError(r, cErr) 30 | default: 31 | serverError := cerror.NewInternalServerError(err, "internal server error") 32 | response.JsonInternalServerError(r, serverError) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /llm_deploy/docker-compose.codellama.yaml: -------------------------------------------------------------------------------- 1 | version: "3.9" 2 | 3 | networks: 4 | default: 5 | driver: bridge 6 | ipam: 7 | config: 8 | - subnet: 192.168.4.1/24 9 | 10 | services: 11 | codellama-13b-hf-server: 12 | build: 13 | context: . 14 | dockerfile: Dockerfile 15 | volumes: 16 | - :/root/.cache/huggingface 17 | image: vllm/vllm-openai:v0.5.0 18 | shm_size: "8g" 19 | deploy: 20 | resources: 21 | reservations: 22 | devices: 23 | - driver: "nvidia" 24 | device_ids: ["0"] 25 | capabilities: ["gpu"] 26 | ports: 27 | - "9000:9000" 28 | entrypoint: ["python3", "-m", "vllm.entrypoints.openai.api_server", "--dtype", "bfloat16", "--max-model-len", "7000", "--tokenizer-mode", "auto", "--max-num-batched-tokens", '30000', "--block-size", "16", "--swap-space", "16", "--served-model-name", 'codellama-13b-hf', "--model", "/root/.cache/huggingface/CodeLlama-13b-hf", "--host", "0.0.0.0", "--port", "9000"] 29 | -------------------------------------------------------------------------------- /llm_deploy/docker-compose.deepseker.yaml: -------------------------------------------------------------------------------- 1 | version: "3.9" 2 | 3 | networks: 4 | default: 5 | driver: bridge 6 | ipam: 7 | config: 8 | - subnet: 192.168.5.1/24 9 | 10 | services: 11 | deepseek-coder-7b-base-server: 12 | build: 13 | context: . 14 | dockerfile: Dockerfile 15 | volumes: 16 | - :/root/.cache/huggingface 17 | image: vllm/vllm-openai:v0.5.0 18 | shm_size: "8g" 19 | deploy: 20 | resources: 21 | reservations: 22 | devices: 23 | - driver: "nvidia" 24 | device_ids: ["1"] 25 | capabilities: ["gpu"] 26 | ports: 27 | - "13000:13000" 28 | entrypoint: ["python3", "-m", "vllm.entrypoints.openai.api_server", "--dtype", "bfloat16", "--max-model-len", "7000", "--tokenizer-mode", "auto", "--max-num-batched-tokens", '30000', "--block-size", "16", "--swap-space", "16", "--served-model-name", 'deepseek-coder-7b-base', "--model", "/root/.cache/huggingface/Deepseek-coder-6.7b-base", "--host", "0.0.0.0", "--port", "13000"] 29 | -------------------------------------------------------------------------------- /core/prompts/code/code.go: -------------------------------------------------------------------------------- 1 | package code 2 | 3 | import ( 4 | "github.com/tmc/langchaingo/prompts" 5 | ) 6 | 7 | func GenerateCodeLLamaPrompt(prefix string, suffix string) (string, error) { 8 | template := `{{.before_code}}{{.after_code}}` 9 | 10 | promptTemplate := prompts.NewPromptTemplate(template, []string{"before_code", "after_code"}) 11 | prompt, err := promptTemplate.FormatPrompt(map[string]any{ 12 | "before_code": prefix, 13 | "after_code": suffix, 14 | }) 15 | if err != nil { 16 | return "", err 17 | } 18 | 19 | return prompt.String(), nil 20 | } 21 | 22 | func GenerateDeepSeekerPrompt(prefix string, suffix string) (string, error) { 23 | template := `<|fim▁begin|>{{.before_code}}<|fim▁hole|>{{.after_code}}<|fim▁end|>` 24 | 25 | promptTemplate := prompts.NewPromptTemplate(template, []string{"before_code", "after_code"}) 26 | prompt, err := promptTemplate.FormatPrompt(map[string]any{ 27 | "before_code": prefix, 28 | "after_code": suffix, 29 | }) 30 | if err != nil { 31 | return "", err 32 | } 33 | 34 | return prompt.String(), nil 35 | } 36 | -------------------------------------------------------------------------------- /manifest/config/xcoder_code_generate_llm_params_conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "codellama": { 4 | "modelName": "codellama", 5 | "modelVersion": "codellama-13b-hf", 6 | "totalMaxTokens": 2500, 7 | "singleLineMaxTokens": 80, 8 | "multiLineMaxTokens": 160, 9 | "singeLineTemperature": 0.2, 10 | "multiLineTemperature": 0, 11 | "singleLineStopWords": ["\n", "\r\n"], 12 | "multiLineStopWords": ["\n\n", "\r\n\r\n"], 13 | "singleLineTopP": 1, 14 | "multiLineTopP": 1, 15 | "connUrls": [] 16 | }, 17 | "deepseeker": { 18 | "modelName": "deepseeker", 19 | "modelVersion": "deepseek-coder-7b-base", 20 | "totalMaxTokens": 2500, 21 | "singleLineMaxTokens": 80, 22 | "multiLineMaxTokens": 160, 23 | "singeLineTemperature": 0.2, 24 | "multiLineTemperature": 0, 25 | "singleLineStopWords": ["\n", "\r\n"], 26 | "multiLineStopWords": ["\n\n", "\r\n\r\n"], 27 | "singleLineTopP": 1, 28 | "multiLineTopP": 1, 29 | "connUrls": [] 30 | } 31 | }, 32 | "selectedModel": "codellama" 33 | } -------------------------------------------------------------------------------- /internal/cmd/cmd.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "context" 5 | "xcoder/internal/router" 6 | "xcoder/internal/service" 7 | 8 | "github.com/gogf/gf/v2/frame/g" 9 | "github.com/gogf/gf/v2/net/ghttp" 10 | "github.com/gogf/gf/v2/os/gcmd" 11 | ) 12 | 13 | var ( 14 | Main = gcmd.Command{ 15 | Name: "main", 16 | Usage: "main", 17 | Brief: "start http server", 18 | Description: "默认启动所有服务", 19 | Func: func(ctx context.Context, parser *gcmd.Parser) (err error) { 20 | g.Log().Debug(ctx, "start http server") 21 | 22 | s := g.Server() 23 | 24 | s.BindMiddleware("/*any", []ghttp.HandlerFunc{ 25 | ghttp.MiddlewareCORS, 26 | service.Middleware().RecoveryHandler, 27 | service.Middleware().ErrResponseHandler, 28 | }...) 29 | 30 | s.Group("/", func(group *ghttp.RouterGroup) { 31 | router.Hs(ctx, group) 32 | router.Code(ctx, group) 33 | router.Chat(ctx, group) 34 | router.UnitTest(ctx, group) 35 | router.Explain(ctx, group) 36 | router.Optimize(ctx, group) 37 | router.Edit(ctx, group) 38 | router.Comment(ctx, group) 39 | }) 40 | 41 | s.Run() 42 | return nil 43 | }, 44 | } 45 | ) 46 | -------------------------------------------------------------------------------- /docs/intro.md: -------------------------------------------------------------------------------- 1 | # XCoder 2 | ## 产品概述 3 | ### 1. XCoder是什么? 4 | XCoder是一款基于大语言模型(LLM)的编码辅助工具,旨在提供智能化的代码推理和辅助功能。 5 | XCoder的开发灵感来源于类似产品Copilot 的代码自动补全功能,无论是在功能还是性能方面都基于我们公司的现状进行了的优化和改良。XCoder目前已经过多个内测版本的迭代和用户反馈的持续优化。 6 | ### 2. 核心功能和特点 7 | | 分类 | 支持的语言 | 8 | |--------|------------| 9 | | 后端 | java、golang、python、c++、php... | 10 | | 前端 | javascript、typescript、html... | 11 | | 其他 | yaml、markdown、json... | 12 | 13 | | 功能 | 描述 | 14 | |-----------|----------------------------------------------------| 15 | | 单行、多行代码补全 | 根据用户当前上下文及 IDE 打开的其他文件获取 context ,生成接下来一行或者多行代码的内容 | 16 | | 智能chat | 提供对编码问题的智能回答和解决方案,帮助用户快速解决问题 | 17 | | 单元测试 | 根据所选函数及上下文,智能生成代码的单元测试用例 | 18 | | 代码文档及注释生成 | 根据所选代码,生成代码文档及代码注释 | 19 | | 代码优化 | 智能修改、重构、优化用户所选的代码 | 20 | | 代码解释 | 根据所选代码,生成对代码的功能解释 | 21 | | 代码编辑 | 根据所选代码,生成注释或者说明文档 | 22 | 23 | ### 3. 优势和价值 24 | 1.提高编码效率:通过智能推理和代码补全功能,加速编码过程,减少重复劳动。 25 | 2.提升代码质量:自动生成单元测试用例和提供代码修复建议,帮助用户编写更可靠和高质量的代码。 26 | 3.加速学习曲线:对于初学者来说,XCoder提供了实时的代码推理和建议,帮助他们理解和学习编码。 27 | 28 | -------------------------------------------------------------------------------- /internal/model/do/p_code_gen_retrieval_snippet_map.go: -------------------------------------------------------------------------------- 1 | // ================================================================================= 2 | // Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. 3 | // ================================================================================= 4 | 5 | package do 6 | 7 | import ( 8 | "github.com/gogf/gf/v2/frame/g" 9 | "github.com/gogf/gf/v2/os/gtime" 10 | ) 11 | 12 | // PCodeGenRetrievalSnippetMap is the golang structure of table p_code_gen_retrieval_snippet_map for DAO operations like Where/Data. 13 | type PCodeGenRetrievalSnippetMap struct { 14 | g.Meta `orm:"table:p_code_gen_retrieval_snippet_map, do:true"` 15 | Updatetime *gtime.Time // 16 | Inserttime *gtime.Time // 17 | Isactive interface{} // 18 | DeleteToken interface{} // 19 | Id interface{} // 20 | GenerateUuid interface{} // 21 | GitRepo interface{} // 22 | GitBranch interface{} // 23 | SnippetUuid interface{} // 24 | SnippetScore interface{} // 25 | SnippetRepo interface{} // 26 | SnippetPath interface{} // 27 | SnippetContent interface{} // 28 | ProjectName interface{} // 29 | ProjectVersion interface{} // 30 | CreateUser interface{} // 31 | } 32 | -------------------------------------------------------------------------------- /utility/xconcurrent/compute.go: -------------------------------------------------------------------------------- 1 | package xconcurrent 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "github.com/gogf/gf/v2/frame/g" 8 | "time" 9 | ) 10 | 11 | type Base struct { 12 | name string 13 | m chan struct{} 14 | Err error 15 | } 16 | 17 | func NewBase(name string) *Base { 18 | return &Base{ 19 | name: fmt.Sprintf("dao/%s", name), 20 | m: make(chan struct{}), 21 | } 22 | } 23 | 24 | func (b *Base) Name() string { 25 | return b.name 26 | } 27 | 28 | func (b *Base) Wait() { 29 | <-b.m 30 | } 31 | 32 | func (b *Base) Close() { 33 | close(b.m) 34 | } 35 | 36 | func (b *Base) Done() <-chan struct{} { 37 | return b.m 38 | } 39 | 40 | func (b *Base) Compute(ctx context.Context, f func(context.Context) error) { 41 | go func() { 42 | defer func() { 43 | if r := recover(); r != nil { 44 | errMsg := fmt.Sprintf("panic in %s, err: %v", b.name, r) 45 | g.Log().Error(ctx, errMsg) 46 | } 47 | }() 48 | if err := f(ctx); err != nil { 49 | err := fmt.Errorf("error in %s, err: %w", b.name, err) 50 | b.Err = err 51 | errMsg := err.Error() 52 | if errors.Is(err, context.Canceled) { 53 | g.Log().Infof(ctx, "time: %v event cancel error.kind, Dao Compute Canceled, "+ 54 | "message: %s", time.Now(), errMsg, 55 | ) 56 | return 57 | } 58 | g.Log().Error(ctx, errMsg) 59 | } 60 | }() 61 | } 62 | -------------------------------------------------------------------------------- /core/prompts/explain/explain.go: -------------------------------------------------------------------------------- 1 | package explain 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "xcoder/internal/model/input/explainin" 7 | ) 8 | 9 | const SystemMessage = `你是XCoder,一个由XinYe公司开发的AI编程助手。当被问及你的名字时,你需要回答“XCoder”。 10 | 你需要严格按照用户的要求行事。以下是你的工作方式: 11 | 1. 每个代码块都以%[1]s开始。 12 | 2. 你总是用%[2]s语言回答。 13 | 3. 当用户要求你提供代码时,你需要以%[2]s代码块的形式回答。 14 | 4. 在答案中使用 Markdown 格式。 15 | 5. 你的答案尽量简短且仅限于技术领域。 16 | 请注意,你是一个AI编程助手,只对软件开发相关的问题进行回答。` 17 | 18 | const userInstruction = `要求以简单易懂的方式向初级程序员解释一段代码。需要说明代码的目的、输入、输出,以及它是如何通过逻辑和算法实现目的的。 19 | 同时,要指出代码中的重要逻辑流程和数据转换,但避免过于技术性的语言。解释应该清晰、连贯、语法正确,无需逐行解释,针对整个代码块进行解释即可。` 20 | 21 | func GenerateExplainPrompt(ctx context.Context, in *explainin.CodeExplainPromptRequest) ([]map[string]string, error) { 22 | tmpls := []map[string]string{ 23 | { 24 | "role": "system", 25 | "content": fmt.Sprintf(SystemMessage, "```", in.CodeLanguage), 26 | }, 27 | } 28 | 29 | selectedTmpl := []map[string]string{ 30 | { 31 | "role": "user", 32 | "content": fmt.Sprintf("我从文件: `%s`,挑选的 %s 代码,代码内容为:\n```%s\n%s\n```", 33 | in.CodeLanguage, in.CodePath, in.CodeLanguage, in.SelectedCode), 34 | }, 35 | } 36 | 37 | endTmpl := []map[string]string{ 38 | { 39 | "role": "user", 40 | "content": userInstruction, 41 | }, 42 | } 43 | tmpls = append(tmpls, selectedTmpl...) 44 | tmpls = append(tmpls, endTmpl...) 45 | 46 | return tmpls, nil 47 | } 48 | -------------------------------------------------------------------------------- /core/prompts/comment/comment.go: -------------------------------------------------------------------------------- 1 | package comment 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "xcoder/internal/model/input/commentin" 7 | ) 8 | 9 | const SystemMessage = `你是XCoder,一个由XinYe公司开发的AI编程助手。当被问及你的名字时,你需要回答“XCoder”。 10 | 你需要严格按照用户的要求行事。以下是你的工作方式: 11 | 1. 每个代码块都以%[1]s开始。 12 | 2. 你总是用%[2]s语言回答。 13 | 3. 当用户要求你提供代码时,你需要以%[2]s代码块的形式回答。 14 | 4. 在答案中使用 Markdown 格式。 15 | 5. 你的答案尽量简短且仅限于技术领域。 16 | 请注意,你是一个AI编程助手,只对软件开发相关的问题进行回答。` 17 | 18 | const userInstruction = `1. 请给选中代码增加函数文档以及代码注释,以便让代码更易于理解。 19 | 2. 如果选中代码中存在代码注释,则将其作为示例使用,遵循其相同的风格(生成语言,行数等)返回结果,不要修改已有的代码注释。 20 | 3. 仅仅是给选中代码添加注释,不要修改代码逻辑。 21 | 4. 生成的函数文档应该根据%s语言特性,生成在合适的位置。 22 | 5. 生成的代码注释必须都放在代码所在行的上面一行。 23 | 6. 生成的函数文档及代码注释使用中文返回。` 24 | 25 | func GenerateCommentPrompt(ctx context.Context, in *commentin.GenerateCommentPromptRequest) ([]map[string]string, error) { 26 | tmpls := []map[string]string{ 27 | { 28 | "role": "system", 29 | "content": fmt.Sprintf(SystemMessage, "```", in.CodeLanguage), 30 | }, 31 | } 32 | 33 | selectedTmpl := []map[string]string{ 34 | { 35 | "role": "user", 36 | "content": fmt.Sprintf("我从文件: `%s`,挑选的 %s 代码,代码内容为:\n```%s\n%s\n```", 37 | in.CodeLanguage, in.CodePath, in.CodeLanguage, in.SelectedCode), 38 | }, 39 | } 40 | 41 | endTmpl := []map[string]string{ 42 | { 43 | "role": "user", 44 | "content": fmt.Sprintf(userInstruction, in.CodeLanguage), 45 | }, 46 | } 47 | tmpls = append(tmpls, selectedTmpl...) 48 | tmpls = append(tmpls, endTmpl...) 49 | 50 | return tmpls, nil 51 | } 52 | -------------------------------------------------------------------------------- /internal/controller/chat/chat_v1.go: -------------------------------------------------------------------------------- 1 | package chat 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/gogf/gf/v2/frame/g" 7 | "github.com/gogf/gf/v2/util/gconv" 8 | "xcoder/api/chat/v1" 9 | "xcoder/internal/controller/utils" 10 | "xcoder/internal/model/input/chatin" 11 | "xcoder/internal/service" 12 | "xcoder/utility/xruntime" 13 | ) 14 | 15 | type ControllerV1 struct{} 16 | 17 | func NewV1() *ControllerV1 { 18 | return &ControllerV1{} 19 | } 20 | 21 | func (c *ControllerV1) SseGenerate(ctx context.Context, req *v1.ChatSseGenerateReq) ( 22 | resp *v1.ChatSseGenerateRes, err error) { 23 | r := utils.GetStreamingChatReq(ctx) 24 | 25 | result := make(chan *chatin.ChatResult) 26 | // 异步获取 chat 结果 27 | go func() { 28 | defer close(result) 29 | defer func() { 30 | if r := recover(); r != nil { 31 | errMsg := fmt.Sprintf("panic in %s, err: %v", "chat.Call", r) 32 | mstack := xruntime.MStack(2, 5) 33 | errMsgWithStack := fmt.Sprintf("%s, stack:\n%s", errMsg, mstack) 34 | g.Log().Error(ctx, errMsgWithStack) 35 | } 36 | }() 37 | 38 | in := &chatin.ChatSseGenerateReq{} 39 | err := gconv.Struct(req, in) 40 | if err != nil { 41 | return 42 | } 43 | err = service.Chat().SseGenerate(ctx, in, result) 44 | if err != nil { 45 | g.Log().Errorf(ctx, "ChatGenerate Call err: %v", err) 46 | result <- &chatin.ChatResult{Error: err, Data: ""} 47 | return 48 | } 49 | }() 50 | 51 | utils.ParseStreamingChatResp(ctx, r, result) 52 | 53 | return nil, nil 54 | } 55 | -------------------------------------------------------------------------------- /internal/model/input/commentin/comment.go: -------------------------------------------------------------------------------- 1 | package commentin 2 | 3 | type UserContext struct { 4 | Type string `json:"type" binding:"omitempty"` 5 | Path string `json:"path" binding:"omitempty"` 6 | Content string `json:"content" binding:"omitempty"` 7 | } 8 | 9 | type CodeCommentRequest struct { 10 | CreateUser string `json:"createUser" binding:"required"` 11 | ConversationUUID string `json:"conversationUUID" binding:"required"` 12 | // git 信息 13 | GitRepo string `json:"gitRepo" binding:"omitempty"` 14 | GitBranch string `json:"gitBranch" binding:"omitempty"` 15 | // code 信息 16 | CodePath string `json:"codePath" binding:"omitempty"` 17 | CodeLanguage string `json:"codeLanguage" binding:"omitempty"` 18 | // IDE 信息 19 | IdeInfo string `json:"ideInfo" binding:"required"` 20 | ProjectVersion string `json:"projectVersion" binding:"required"` 21 | // 代码信息 22 | UserCode string `json:"userCode" binding:"required"` 23 | UserContext []*UserContext `json:"userContext" binding:"omitempty"` 24 | } 25 | 26 | type CodeCommentResponse struct{} 27 | 28 | type GenerateCommentPromptRequest struct { 29 | RepoName string `json:"repoName" binding:"required"` 30 | CodePath string `json:"codePath" binding:"required"` 31 | CodeLanguage string `json:"codeLanguage" binding:"required"` 32 | SelectedCode string `json:"selectCode" binding:"required"` 33 | SharedContexts []*UserContext `json:"sharedContexts" binding:"required"` 34 | } 35 | -------------------------------------------------------------------------------- /internal/model/input/optimizein/optimize.go: -------------------------------------------------------------------------------- 1 | package optimizein 2 | 3 | type UserContext struct { 4 | Type string `json:"type" binding:"omitempty"` 5 | Path string `json:"path" binding:"omitempty"` 6 | Content string `json:"content" binding:"omitempty"` 7 | } 8 | 9 | type OptimizeSseGenerateRequest struct { 10 | CreateUser string `json:"createUser" binding:"required"` 11 | ConversationUUID string `json:"conversationUUID" binding:"required"` 12 | // git 信息 13 | GitRepo string `json:"gitRepo" binding:"omitempty"` 14 | GitBranch string `json:"gitBranch" binding:"omitempty"` 15 | // code 信息 16 | CodePath string `json:"codePath" binding:"omitempty"` 17 | CodeLanguage string `json:"codeLanguage" binding:"omitempty"` 18 | // IDE 信息 19 | IdeInfo string `json:"ideInfo" binding:"required"` 20 | ProjectVersion string `json:"projectVersion" binding:"required"` 21 | // 代码信息 22 | UserCode string `json:"userCode" binding:"required"` 23 | UserContext []*UserContext `json:"userContext" binding:"omitempty"` 24 | } 25 | 26 | type OptimizeSseGenerateResponse struct{} 27 | 28 | type GenerateCodeOptimizePromptRequest struct { 29 | RepoName string `json:"repoName" binding:"required"` 30 | CodePath string `json:"codePath" binding:"required"` 31 | CodeLanguage string `json:"codeLanguage" binding:"required"` 32 | SelectedCode string `json:"selectCode" binding:"required"` 33 | SharedContexts []*UserContext `json:"sharedContexts" binding:"required"` 34 | } 35 | -------------------------------------------------------------------------------- /internal/controller/edit/edit_v1.go: -------------------------------------------------------------------------------- 1 | package edit 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/gogf/gf/v2/frame/g" 7 | "github.com/gogf/gf/v2/util/gconv" 8 | "xcoder/api/edit/v1" 9 | "xcoder/internal/controller/utils" 10 | "xcoder/internal/model/input/chatin" 11 | "xcoder/internal/model/input/editin" 12 | "xcoder/internal/service" 13 | "xcoder/utility/xruntime" 14 | ) 15 | 16 | type ControllerV1 struct{} 17 | 18 | func NewV1() *ControllerV1 { 19 | return &ControllerV1{} 20 | } 21 | 22 | func (c *ControllerV1) SseGenerate(ctx context.Context, req *v1.EditSseGenerateReq) ( 23 | resp *v1.EditSseGenerateRes, err error) { 24 | r := utils.GetStreamingChatReq(ctx) 25 | 26 | result := make(chan *chatin.ChatResult) 27 | // 异步获取 chat 结果 28 | go func() { 29 | defer close(result) 30 | defer func() { 31 | if r := recover(); r != nil { 32 | errMsg := fmt.Sprintf("panic in %s, err: %v", "chat.Call", r) 33 | mstack := xruntime.MStack(2, 5) 34 | errMsgWithStack := fmt.Sprintf("%s, stack:\n%s", errMsg, mstack) 35 | g.Log().Error(ctx, errMsgWithStack) 36 | } 37 | }() 38 | 39 | in := &editin.EditSseGenerateRequest{} 40 | err := gconv.Struct(req, in) 41 | if err != nil { 42 | return 43 | } 44 | err = service.Edit().SseGenerate(ctx, in, result) 45 | if err != nil { 46 | g.Log().Errorf(ctx, "ChatGenerate Call err: %v", err) 47 | result <- &chatin.ChatResult{Error: err, Data: ""} 48 | return 49 | } 50 | }() 51 | 52 | utils.ParseStreamingChatResp(ctx, r, result) 53 | 54 | return nil, nil 55 | } 56 | -------------------------------------------------------------------------------- /core/prompts/optimize/optimize.go: -------------------------------------------------------------------------------- 1 | package optimize 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "xcoder/internal/model/input/optimizein" 7 | ) 8 | 9 | const SystemMessage = `你是XCoder,一个由XinYe公司开发的AI编程助手。当被问及你的名字时,你需要回答“XCoder”。 10 | 你需要严格按照用户的要求行事。以下是你的工作方式: 11 | 1. 每个代码块都以%[1]s开始。 12 | 2. 你总是用%[2]s语言回答。 13 | 3. 当用户要求你提供代码时,你需要以%[2]s代码块的形式回答。 14 | 4. 在答案中使用 Markdown 格式。 15 | 5. 你的答案尽量简短且仅限于技术领域。 16 | 请注意,你是一个AI编程助手,只对软件开发相关的问题进行回答。` 17 | 18 | const userInstruction = `优化用户选中代码。你可以按照以下步骤进行代码优化: 19 | 1. 理解用户代码:仔细阅读用户提供的代码。 20 | 2. 问题检测:分析代码逻辑、性能、安全性等方面,寻找潜在的缺陷或改进点。 21 | 3. 提供解决方案:如果发现问题,提出优化建议并提供相应的代码示例;如果未检测到问题,提醒用户准确描述需要修复的内容。 22 | 4. 逐步解释:在给出代码之前,先阐述编写计划,以便用户了解优化的思路和步骤。 23 | 5. 代码输出:将优化后的代码放入代码块中,确保清晰易读。 24 | 注意:除了代码部分,其他总结性语句必须用中文返回。` 25 | 26 | func GenerateOptimizePrompt(ctx context.Context, in *optimizein.GenerateCodeOptimizePromptRequest) ([]map[string]string, error) { 27 | tmpls := []map[string]string{ 28 | { 29 | "role": "system", 30 | "content": fmt.Sprintf(SystemMessage, "```", in.CodeLanguage), 31 | }, 32 | } 33 | 34 | selectedTmpl := []map[string]string{ 35 | { 36 | "role": "user", 37 | "content": fmt.Sprintf("我从文件: `%s`,挑选的 %s 代码,代码内容为:\n```%s\n%s\n```", 38 | in.CodeLanguage, in.CodePath, in.CodeLanguage, in.SelectedCode), 39 | }, 40 | } 41 | 42 | userInstructionTmpl := []map[string]string{ 43 | { 44 | "role": "user", 45 | "content": userInstruction, 46 | }, 47 | } 48 | 49 | tmpls = append(tmpls, selectedTmpl...) 50 | tmpls = append(tmpls, userInstructionTmpl...) 51 | 52 | return tmpls, nil 53 | } 54 | -------------------------------------------------------------------------------- /internal/controller/comment/comment_v1.go: -------------------------------------------------------------------------------- 1 | package comment 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/gogf/gf/v2/frame/g" 7 | "github.com/gogf/gf/v2/util/gconv" 8 | "xcoder/api/comment/v1" 9 | "xcoder/internal/controller/utils" 10 | "xcoder/internal/model/input/chatin" 11 | "xcoder/internal/model/input/commentin" 12 | "xcoder/internal/service" 13 | "xcoder/utility/xruntime" 14 | ) 15 | 16 | type ControllerV1 struct{} 17 | 18 | func NewV1() *ControllerV1 { 19 | return &ControllerV1{} 20 | } 21 | 22 | func (c *ControllerV1) SseGenerate(ctx context.Context, req *v1.CommentSseGenerateReq) ( 23 | resp *v1.CommentSseGenerateRes, err error) { 24 | r := utils.GetStreamingChatReq(ctx) 25 | 26 | result := make(chan *chatin.ChatResult) 27 | // 异步获取 chat 结果 28 | go func() { 29 | defer close(result) 30 | defer func() { 31 | if r := recover(); r != nil { 32 | errMsg := fmt.Sprintf("panic in %s, err: %v", "chat.Call", r) 33 | mstack := xruntime.MStack(2, 5) 34 | errMsgWithStack := fmt.Sprintf("%s, stack:\n%s", errMsg, mstack) 35 | g.Log().Error(ctx, errMsgWithStack) 36 | } 37 | }() 38 | 39 | in := &commentin.CodeCommentRequest{} 40 | err := gconv.Struct(req, in) 41 | if err != nil { 42 | return 43 | } 44 | err = service.Comment().SseGenerate(ctx, in, result) 45 | if err != nil { 46 | g.Log().Errorf(ctx, "ChatGenerate Call err: %v", err) 47 | result <- &chatin.ChatResult{Error: err, Data: ""} 48 | return 49 | } 50 | }() 51 | 52 | utils.ParseStreamingChatResp(ctx, r, result) 53 | 54 | return nil, nil 55 | } 56 | -------------------------------------------------------------------------------- /internal/controller/explain/explain_v1.go: -------------------------------------------------------------------------------- 1 | package explain 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/gogf/gf/v2/frame/g" 7 | "github.com/gogf/gf/v2/util/gconv" 8 | "xcoder/api/explain/v1" 9 | "xcoder/internal/controller/utils" 10 | "xcoder/internal/model/input/chatin" 11 | "xcoder/internal/model/input/explainin" 12 | "xcoder/internal/service" 13 | "xcoder/utility/xruntime" 14 | ) 15 | 16 | type ControllerV1 struct{} 17 | 18 | func NewV1() *ControllerV1 { 19 | return &ControllerV1{} 20 | } 21 | 22 | func (c *ControllerV1) SseGenerate(ctx context.Context, req *v1.ExplainSseGenerateReq) ( 23 | resp *v1.ExplainSseGenerateRes, err error) { 24 | r := utils.GetStreamingChatReq(ctx) 25 | 26 | result := make(chan *chatin.ChatResult) 27 | // 异步获取 chat 结果 28 | go func() { 29 | defer close(result) 30 | defer func() { 31 | if r := recover(); r != nil { 32 | errMsg := fmt.Sprintf("panic in %s, err: %v", "chat.Call", r) 33 | mstack := xruntime.MStack(2, 5) 34 | errMsgWithStack := fmt.Sprintf("%s, stack:\n%s", errMsg, mstack) 35 | g.Log().Error(ctx, errMsgWithStack) 36 | } 37 | }() 38 | 39 | in := &explainin.ExplainSseGenerateRequest{} 40 | err := gconv.Struct(req, in) 41 | if err != nil { 42 | return 43 | } 44 | err = service.Explain().SseGenerate(ctx, in, result) 45 | if err != nil { 46 | g.Log().Errorf(ctx, "ChatGenerate Call err: %v", err) 47 | result <- &chatin.ChatResult{Error: err, Data: ""} 48 | return 49 | } 50 | }() 51 | 52 | utils.ParseStreamingChatResp(ctx, r, result) 53 | 54 | return nil, nil 55 | } 56 | -------------------------------------------------------------------------------- /internal/controller/unit_test/unit_v1.go: -------------------------------------------------------------------------------- 1 | package unit_test 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/gogf/gf/v2/frame/g" 7 | "github.com/gogf/gf/v2/util/gconv" 8 | "xcoder/api/unit_test/v1" 9 | "xcoder/internal/controller/utils" 10 | "xcoder/internal/model/input/chatin" 11 | "xcoder/internal/model/input/unit_testin" 12 | "xcoder/internal/service" 13 | "xcoder/utility/xruntime" 14 | ) 15 | 16 | type ControllerV1 struct{} 17 | 18 | func NewV1() *ControllerV1 { 19 | return &ControllerV1{} 20 | } 21 | 22 | func (c *ControllerV1) SseGenerate(ctx context.Context, req *v1.UnitTestSseGenerateReq) ( 23 | resp *v1.UnitTestSseGenerateRes, err error) { 24 | r := utils.GetStreamingChatReq(ctx) 25 | 26 | result := make(chan *chatin.ChatResult) 27 | // 异步获取 chat 结果 28 | go func() { 29 | defer close(result) 30 | defer func() { 31 | if r := recover(); r != nil { 32 | errMsg := fmt.Sprintf("panic in %s, err: %v", "chat.Call", r) 33 | mstack := xruntime.MStack(2, 5) 34 | errMsgWithStack := fmt.Sprintf("%s, stack:\n%s", errMsg, mstack) 35 | g.Log().Error(ctx, errMsgWithStack) 36 | } 37 | }() 38 | 39 | in := &unit_testin.UnitTestSseGenerateReq{} 40 | err := gconv.Struct(req, in) 41 | if err != nil { 42 | return 43 | } 44 | err = service.Unit().SseGenerate(ctx, in, result) 45 | if err != nil { 46 | g.Log().Errorf(ctx, "ChatGenerate Call err: %v", err) 47 | result <- &chatin.ChatResult{Error: err, Data: ""} 48 | return 49 | } 50 | }() 51 | 52 | utils.ParseStreamingChatResp(ctx, r, result) 53 | 54 | return nil, nil 55 | } 56 | -------------------------------------------------------------------------------- /internal/controller/optimize/optimize_v1.go: -------------------------------------------------------------------------------- 1 | package optimize 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/gogf/gf/v2/frame/g" 7 | "github.com/gogf/gf/v2/util/gconv" 8 | "xcoder/api/optimize/v1" 9 | "xcoder/internal/controller/utils" 10 | "xcoder/internal/model/input/chatin" 11 | "xcoder/internal/model/input/optimizein" 12 | "xcoder/internal/service" 13 | "xcoder/utility/xruntime" 14 | ) 15 | 16 | type ControllerV1 struct{} 17 | 18 | func NewV1() *ControllerV1 { 19 | return &ControllerV1{} 20 | } 21 | 22 | func (c *ControllerV1) SseGenerate(ctx context.Context, req *v1.OptimizeSseGenerateReq) ( 23 | resp *v1.OptimizeSseGenerateRes, err error) { 24 | r := utils.GetStreamingChatReq(ctx) 25 | 26 | result := make(chan *chatin.ChatResult) 27 | // 异步获取 chat 结果 28 | go func() { 29 | defer close(result) 30 | defer func() { 31 | if r := recover(); r != nil { 32 | errMsg := fmt.Sprintf("panic in %s, err: %v", "Optimize SseGenerate", r) 33 | mstack := xruntime.MStack(2, 5) 34 | errMsgWithStack := fmt.Sprintf("%s, stack:\n%s", errMsg, mstack) 35 | g.Log().Error(ctx, errMsgWithStack) 36 | } 37 | }() 38 | 39 | in := &optimizein.OptimizeSseGenerateRequest{} 40 | err := gconv.Struct(req, in) 41 | if err != nil { 42 | return 43 | } 44 | err = service.Optimize().SseGenerate(ctx, in, result) 45 | if err != nil { 46 | g.Log().Errorf(ctx, "Optimize SseGenerate err: %v", err) 47 | result <- &chatin.ChatResult{Error: err, Data: ""} 48 | return 49 | } 50 | }() 51 | 52 | utils.ParseStreamingChatResp(ctx, r, result) 53 | 54 | return nil, nil 55 | } 56 | -------------------------------------------------------------------------------- /core/schema/schema.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Travis Cline 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package schema 22 | 23 | // PromptValue is the interface that all prompt values must implement. 24 | type PromptValue interface { 25 | String() string 26 | Messages() []ChatMessage 27 | } 28 | 29 | // Generation is the output of a single generation. 30 | type Generation struct { 31 | // Generated text output. 32 | Text string 33 | // Raw generation info response from the provider. 34 | // May include things like reason for finishing (e.g. in OpenAI). 35 | GenerationInfo map[string]any 36 | } 37 | -------------------------------------------------------------------------------- /internal/controller/utils/cerror/cerror.go: -------------------------------------------------------------------------------- 1 | package cerror 2 | 3 | import ( 4 | "fmt" 5 | "xcoder/internal/controller/utils/ccode" 6 | ) 7 | 8 | type CustomError struct { 9 | Code int `json:"code"` 10 | Message string `json:"message"` 11 | RawErr error 12 | } 13 | 14 | type UserError struct { 15 | CustomError 16 | } 17 | 18 | type UserErrorButHttpOk struct { 19 | CustomError 20 | } 21 | 22 | type ServerError struct { 23 | CustomError 24 | } 25 | 26 | type UnauthorizedError struct { 27 | CustomError 28 | } 29 | 30 | func (err CustomError) Error() string { 31 | return err.Message 32 | } 33 | 34 | func (err CustomError) RawError() error { 35 | return err.RawErr 36 | } 37 | 38 | func (e *UserError) Error() string { 39 | return fmt.Sprintf("%v: %v", e.Message, e.RawErr.Error()) 40 | } 41 | 42 | func NewInternalServerError(err error, message string) *ServerError { 43 | return &ServerError{ 44 | CustomError{ 45 | Code: ccode.CodeInternalServerError, 46 | Message: message, 47 | RawErr: err, 48 | }, 49 | } 50 | } 51 | 52 | func NewUserError(err error, code int, message string) *UserError { 53 | return &UserError{ 54 | CustomError{ 55 | Code: code, 56 | Message: message, 57 | RawErr: err, 58 | }, 59 | } 60 | } 61 | 62 | func NewUnauthorizedError(err error, code int, message string) *UnauthorizedError { 63 | return &UnauthorizedError{CustomError{ 64 | Code: code, 65 | Message: message, 66 | RawErr: err, 67 | }} 68 | } 69 | 70 | func NewUserErrorButHttpOk(err error, code int, message string) *UserErrorButHttpOk { 71 | return &UserErrorButHttpOk{ 72 | CustomError{ 73 | Code: code, 74 | Message: message, 75 | RawErr: err, 76 | }, 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /utility/xapollo/chat.go: -------------------------------------------------------------------------------- 1 | package xapollo 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/fsnotify/fsnotify" 7 | "github.com/gogf/gf/v2/frame/g" 8 | "xcoder/internal/model/input/chatin" 9 | ) 10 | 11 | const ( 12 | ChatLLMParamsConfigPath = "xcoder_chat_llm_params_conf" 13 | ) 14 | 15 | var ChatLLMParamsConfigInfo = new(chatin.ChatLLMParamsConfig) 16 | 17 | func init() { 18 | viperCfg := NewViperConfig() 19 | viperCfg.SetConfigName(ChatLLMParamsConfigPath) 20 | if err := viperCfg.ReadInConfig(); err != nil { 21 | g.Log().Errorf(context.Background(), "read config file %s error: %s", ChatLLMParamsConfigPath, err.Error()) 22 | } 23 | if err := viperCfg.Unmarshal(ChatLLMParamsConfigInfo); err != nil { 24 | g.Log().Errorf(context.Background(), "unmarshal config file %s error: %s", ChatLLMParamsConfigPath, err.Error()) 25 | return 26 | } 27 | g.Log().Infof(context.Background(), "load config file %s success", ChatLLMParamsConfigPath) 28 | g.Log().Infof(context.Background(), "ChatLLMParamsConfigInfo params: %+v", ChatLLMParamsConfigInfo.Config.LLMParams) 29 | 30 | viperCfg.WatchConfig() 31 | viperCfg.OnConfigChange(func(in fsnotify.Event) { 32 | if err := viperCfg.Unmarshal(ChatLLMParamsConfigInfo); err != nil { 33 | g.Log().Errorf(context.Background(), "unmarshal config file %s error: %s", ChatLLMParamsConfigPath, err.Error()) 34 | return 35 | } 36 | g.Log().Infof(context.Background(), "load config file %s success", ChatLLMParamsConfigPath) 37 | }) 38 | } 39 | 40 | func GetChatLLMParams() (*chatin.ChatLLMParamsConfig, error) { 41 | if ChatLLMParamsConfigInfo.Config == nil { 42 | return nil, fmt.Errorf("ChatLLMParamsConfigInfo.Config is nil") 43 | } 44 | 45 | return ChatLLMParamsConfigInfo, nil 46 | } 47 | -------------------------------------------------------------------------------- /internal/model/input/explainin/explain.go: -------------------------------------------------------------------------------- 1 | package explainin 2 | 3 | type UserContext struct { 4 | Type string `json:"type" binding:"omitempty"` 5 | Path string `json:"path" binding:"omitempty"` 6 | Content string `json:"content" binding:"omitempty"` 7 | } 8 | 9 | type ExplainSseGenerateRequest struct { 10 | CreateUser string `json:"createUser" binding:"required"` 11 | ConversationUUID string `json:"conversationUUID" binding:"required"` 12 | // git 信息 13 | GitRepo string `json:"gitRepo" binding:"omitempty"` 14 | GitBranch string `json:"gitBranch" binding:"omitempty"` 15 | // code 信息 16 | CodePath string `json:"codePath" binding:"omitempty"` 17 | CodeLanguage string `json:"codeLanguage" binding:"omitempty"` 18 | // IDE 信息 19 | IdeInfo string `json:"ideInfo" binding:"required"` 20 | ProjectVersion string `json:"projectVersion" binding:"required"` 21 | // 代码信息 22 | UserCode string `json:"userCode" binding:"required"` 23 | UserContext []*UserContext `json:"userContext" binding:"omitempty"` 24 | } 25 | 26 | type ExplainSseGenerateResponse struct{} 27 | 28 | type CodeExplainPromptRequest struct { 29 | RepoName string `json:"repoName" binding:"required"` 30 | CodePath string `json:"codePath" binding:"required"` 31 | CodeLanguage string `json:"codeLanguage" binding:"required"` 32 | SelectedCode string `json:"selectCode" binding:"required"` 33 | } 34 | 35 | type CodeFuncAcceptStatusUpdateRequest struct { 36 | ConversationUUID string `json:"conversationUUID" binding:"required"` 37 | AcceptStatus string `json:"acceptStatus" binding:"required"` 38 | } 39 | 40 | type CodeFuncAcceptStatusUpdateResponse struct { 41 | ConversationUUID string `json:"conversationUUID" binding:"required"` 42 | } 43 | -------------------------------------------------------------------------------- /internal/dao/p_code_gen_retrieval_snippet_map.go: -------------------------------------------------------------------------------- 1 | // ================================================================================= 2 | // This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish. 3 | // ================================================================================= 4 | 5 | package dao 6 | 7 | import ( 8 | "context" 9 | "github.com/gogf/gf/v2/frame/g" 10 | "xcoder/internal/dao/internal" 11 | "xcoder/internal/model/entity" 12 | ) 13 | 14 | // internalPCodeGenRetrievalSnippetMapDao is internal type for wrapping internal DAO implements. 15 | type internalPCodeGenRetrievalSnippetMapDao = *internal.PCodeGenRetrievalSnippetMapDao 16 | 17 | // pCodeGenRetrievalSnippetMapDao is the data access object for table p_code_gen_retrieval_snippet_map. 18 | // You can define custom methods on it to extend its functionality as you wish. 19 | type pCodeGenRetrievalSnippetMapDao struct { 20 | internalPCodeGenRetrievalSnippetMapDao 21 | } 22 | 23 | var ( 24 | // PCodeGenRetrievalSnippetMap is globally public accessible object for table p_code_gen_retrieval_snippet_map operations. 25 | PCodeGenRetrievalSnippetMap = pCodeGenRetrievalSnippetMapDao{ 26 | internal.NewPCodeGenRetrievalSnippetMapDao(), 27 | } 28 | ) 29 | 30 | func (dao *pCodeGenRetrievalSnippetMapDao) Create(ctx context.Context, data *entity.PCodeGenRetrievalSnippetMap) error { 31 | _, err := g.Model(PCodeGenRetrievalSnippetMap.Table()).Data(data).Insert() 32 | if err != nil { 33 | return err 34 | } 35 | return nil 36 | } 37 | 38 | func (dao *pCodeGenRetrievalSnippetMapDao) BatchCreate(ctx context.Context, datas []*entity.PCodeGenRetrievalSnippetMap) error { 39 | _, err := g.Model(PCodeGenRetrievalSnippetMap.Table()).Data(datas).Insert() 40 | if err != nil { 41 | return err 42 | } 43 | return nil 44 | 45 | } 46 | -------------------------------------------------------------------------------- /internal/model/do/p_code_gen_records.go: -------------------------------------------------------------------------------- 1 | // ================================================================================= 2 | // Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. 3 | // ================================================================================= 4 | 5 | package do 6 | 7 | import ( 8 | "github.com/gogf/gf/v2/frame/g" 9 | "github.com/gogf/gf/v2/os/gtime" 10 | ) 11 | 12 | // PCodeGenRecords is the golang structure of table p_code_gen_records for DAO operations like Where/Data. 13 | type PCodeGenRecords struct { 14 | g.Meta `orm:"table:p_code_gen_records, do:true"` 15 | Id interface{} // 16 | GenerateUuid interface{} // 17 | GenerateType interface{} // 18 | IsSingleLine interface{} // 19 | CreateUser interface{} // 20 | GitRepo interface{} // 21 | GitBranch interface{} // 22 | CodePath interface{} // 23 | CodeLanguage interface{} // 24 | IdeInfo interface{} // 25 | StartCursorIdx interface{} // 26 | PrefixCodeTokens interface{} // 27 | SuffixCodeTokens interface{} // 28 | ModelName interface{} // 29 | ModelVersion interface{} // 30 | PromptTokens interface{} // 31 | CompletionCode interface{} // 32 | CompletionDuration interface{} // 33 | CompletionCodeTokens interface{} // 34 | CompletionCodeLines interface{} // 35 | FinishReason interface{} // 36 | FailureReason interface{} // 37 | Updatetime *gtime.Time // 38 | Inserttime *gtime.Time // 39 | Isactive interface{} // 40 | DeleteToken interface{} // 41 | AcceptStatus interface{} // 42 | CodeTotalLines interface{} // 43 | CrossfileCtxNums interface{} // 44 | } 45 | -------------------------------------------------------------------------------- /internal/model/input/unit_testin/unit.go: -------------------------------------------------------------------------------- 1 | package unit_testin 2 | 3 | type UserContext struct { 4 | Type string `json:"type" binding:"omitempty"` 5 | Path string `json:"path" binding:"omitempty"` 6 | Content string `json:"content" binding:"omitempty"` 7 | } 8 | 9 | type UnitTestSseGenerateReq struct { 10 | CreateUser string `json:"createUser" binding:"omitempty"` 11 | ConversationUUID string `json:"conversationUUID" binding:"required"` 12 | // git 信息 13 | GitRepo string `json:"gitRepo" binding:"omitempty"` 14 | GitBranch string `json:"gitBranch" binding:"omitempty"` 15 | // code 信息 16 | CodePath string `json:"codePath" binding:"omitempty"` 17 | CodeLanguage string `json:"codeLanguage" binding:"omitempty"` 18 | // IDE 信息 19 | IdeInfo string `json:"ideInfo" binding:"required"` 20 | ProjectVersion string `json:"projectVersion" binding:"required"` 21 | // 测试用例信息 22 | Framework string `json:"unitTestFramework" binding:"omitempty"` 23 | UserText string `json:"userText" binding:"omitempty"` 24 | UserCode string `json:"userCode" binding:"required"` 25 | UserContext []*UserContext `json:"userContext" binding:"omitempty"` 26 | } 27 | 28 | type UnitTestSseGenerateResp struct{} 29 | 30 | type GenerateUTPromptReq struct { 31 | RepoName string `json:"repoName" binding:"required"` 32 | CodePath string `json:"codePath" binding:"required"` 33 | CodeLanguage string `json:"codeLanguage" binding:"required"` 34 | SharedContexts []*UserContext `json:"sharedContexts" binding:"required"` 35 | SelectedCode string `json:"selectedCode" binding:"required"` 36 | UserInstruction string `json:"userInstruction" binding:"omitempty"` 37 | Framework string `json:"framework" binding:"required"` 38 | } 39 | -------------------------------------------------------------------------------- /core/prompts/edit/edit.go: -------------------------------------------------------------------------------- 1 | package edit 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "xcoder/internal/model/input/editin" 7 | ) 8 | 9 | const SystemMessage = `你是XCoder,一个由XinYe公司开发的AI编程助手。当被问及你的名字时,你需要回答“XCoder”。 10 | 你需要严格按照用户的要求行事。以下是你的工作方式: 11 | 1. 每个代码块都以 %[1]s 开始。 12 | 2. 你的回答使用 Markdown 格式。 13 | 3. 你的回答尽量简短且仅限于技术领域。 14 | 请注意,你是一个AI编程助手,只对软件开发相关的问题进行回答。` 15 | 16 | const userInstruction = `你是一个代码编辑器,你的任务是按照用户的要求,参考用户给定的上下文代码片段(如果用户提供),实现编辑用户挑选的代码。你需要按照以下要求进行代码编辑: 17 | - 逐步思考与计划:在开始编辑代码之前,仔细分析用户提供的代码,明确代码的功能和结构。 18 | 确定需要进行的更新或修改,包括哪些功能需要添加、哪些逻辑需要优化。 19 | - 保持代码风格一致:更新后的代码必须与用户提供的代码在缩进、空白和风格上保持一致。` 20 | 21 | func GenerateEditPrompt(ctx context.Context, in *editin.GenerateEditPromptRequest) ([]map[string]string, error) { 22 | tmpls := []map[string]string{ 23 | { 24 | "role": "system", 25 | "content": fmt.Sprintf(SystemMessage, "```", in.CodeLanguage), 26 | }, 27 | } 28 | 29 | for _, c := range in.SharedContexts { 30 | tmpls = append(tmpls, map[string]string{ 31 | "role": "user", 32 | "content": fmt.Sprintf( 33 | "这是用户的一些上下文代码片段,来自文件:`%s`:\n```%s\n%s\n```", 34 | c.Path, in.CodeLanguage, c.Content, 35 | ), 36 | }) 37 | } 38 | 39 | selectedTmpl := []map[string]string{ 40 | { 41 | "role": "user", 42 | "content": fmt.Sprintf("这是用户从文件: `%[1]s`,挑选的 %[2]s 代码,代码内容为:\n```%[2]s\n%[3]s\n```", 43 | in.CodePath, in.CodeLanguage, in.SelectedCode), 44 | }, 45 | } 46 | 47 | endTmpl := []map[string]string{ 48 | { 49 | "role": "user", 50 | "content": userInstruction, 51 | }, 52 | } 53 | 54 | userTmpl := []map[string]string{ 55 | { 56 | "role": "user", 57 | "content": in.UserContent, 58 | }, 59 | } 60 | 61 | tmpls = append(tmpls, selectedTmpl...) 62 | tmpls = append(tmpls, endTmpl...) 63 | tmpls = append(tmpls, userTmpl...) 64 | 65 | return tmpls, nil 66 | } 67 | -------------------------------------------------------------------------------- /internal/model/entity/p_code_gen_retrieval_snippet_map.go: -------------------------------------------------------------------------------- 1 | // ================================================================================= 2 | // Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. 3 | // ================================================================================= 4 | 5 | package entity 6 | 7 | import ( 8 | "github.com/gogf/gf/v2/os/gtime" 9 | ) 10 | 11 | // PCodeGenRetrievalSnippetMap is the golang structure for table p_code_gen_retrieval_snippet_map. 12 | type PCodeGenRetrievalSnippetMap struct { 13 | Updatetime *gtime.Time `json:"updatetime" orm:"updatetime" ` // 14 | Inserttime *gtime.Time `json:"inserttime" orm:"inserttime" ` // 15 | Isactive int `json:"isactive" orm:"isactive" ` // 16 | DeleteToken string `json:"deleteToken" orm:"delete_token" ` // 17 | Id uint `json:"id" orm:"id" ` // 18 | GenerateUuid string `json:"generateUuid" orm:"generate_uuid" ` // 19 | GitRepo string `json:"gitRepo" orm:"git_repo" ` // 20 | GitBranch string `json:"gitBranch" orm:"git_branch" ` // 21 | SnippetUuid string `json:"snippetUuid" orm:"snippet_uuid" ` // 22 | SnippetScore float64 `json:"snippetScore" orm:"snippet_score" ` // 23 | SnippetRepo string `json:"snippetRepo" orm:"snippet_repo" ` // 24 | SnippetPath string `json:"snippetPath" orm:"snippet_path" ` // 25 | SnippetContent string `json:"snippetContent" orm:"snippet_content" ` // 26 | ProjectName string `json:"projectName" orm:"project_name" ` // 27 | ProjectVersion string `json:"projectVersion" orm:"project_version" ` // 28 | CreateUser string `json:"createUser" orm:"create_user" ` // 29 | } 30 | -------------------------------------------------------------------------------- /internal/model/do/p_chat_records.go: -------------------------------------------------------------------------------- 1 | // ================================================================================= 2 | // Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. 3 | // ================================================================================= 4 | 5 | package do 6 | 7 | import ( 8 | "github.com/gogf/gf/v2/frame/g" 9 | "github.com/gogf/gf/v2/os/gtime" 10 | ) 11 | 12 | // PChatRecords is the golang structure of table p_chat_records for DAO operations like Where/Data. 13 | type PChatRecords struct { 14 | g.Meta `orm:"table:p_chat_records, do:true"` 15 | Id interface{} // 序号 16 | ConversationUuid interface{} // 会话 uuid 17 | CreateUser interface{} // 创建人 18 | GitRepo interface{} // git 仓库 19 | GitBranch interface{} // git 分支 20 | CodePath interface{} // 代码路径 21 | CodeLanguage interface{} // 代码语言 22 | IdeInfo interface{} // ide 版本信息 23 | ProjectName interface{} // 插件名称 24 | ProjectVersion interface{} // 插件版本 25 | EngineName interface{} // 模型名称 26 | ModelName interface{} // 模型名称 27 | ModelVersion interface{} // 模型版本 28 | PromptTokens interface{} // prompt tokens 29 | CompletionTokens interface{} // 生成代码 tokens 30 | TotalTokens interface{} // 总 tokens 31 | CompletionCodeLines interface{} // 生成代码行数 32 | CompletionDuration interface{} // 生成代码耗时 33 | FailureReason interface{} // 会话失败原因 34 | AcceptStatus interface{} // 会话是否被采纳 35 | Updatetime *gtime.Time // 更新时间 36 | Inserttime *gtime.Time // 插入时间 37 | Isactive interface{} // 逻辑删除 38 | DeleteToken interface{} // 逻辑删除标志 39 | ConversationType interface{} // 会话类型 40 | PromptTmplVersion interface{} // prompt 版本 41 | PromptTmplContent interface{} // prompt 内容 42 | } 43 | -------------------------------------------------------------------------------- /internal/dao/mongodb/chat.go: -------------------------------------------------------------------------------- 1 | package mongodb 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "go.mongodb.org/mongo-driver/bson" 7 | "google.golang.org/protobuf/types/known/emptypb" 8 | "time" 9 | "xcoder/internal/model/input/chatin" 10 | ) 11 | 12 | func (d *MongodbDao) ChatMessageInsert(ctx context.Context, in *chatin.ChatMessageInsertMongoRequest) ( 13 | *emptypb.Empty, error) { 14 | client, err := d.GetClient(ctx, d.DbName) 15 | if err != nil { 16 | return nil, err 17 | } 18 | 19 | currentTime := time.Now() 20 | year, month, _ := currentTime.Date() 21 | collectionName := fmt.Sprintf("chat_record_%d_%02d", year, int(month)) 22 | collection := client.Database(d.DbName).Collection(collectionName) 23 | _, err = collection.InsertOne(ctx, bson.M{ 24 | "insertTime": currentTime, 25 | "updateTime": currentTime, 26 | "conversationUUID": in.ConversationUUID, 27 | "selectedCode": in.SelectedCode, 28 | "messages": in.Messages, 29 | }) 30 | if err != nil { 31 | return nil, err 32 | } 33 | 34 | return nil, nil 35 | } 36 | 37 | func (d *MongodbDao) ChatMessageUpdate(ctx context.Context, in *chatin.ChatMessageUpdateMongoRequest) ( 38 | *emptypb.Empty, error) { 39 | client, err := d.GetClient(ctx, d.DbName) 40 | if err != nil { 41 | return nil, err 42 | } 43 | 44 | currentTime := time.Now() 45 | year, month, _ := currentTime.Date() 46 | collectionName := fmt.Sprintf("chat_record_%d_%02d", year, int(month)) 47 | collection := client.Database(d.DbName).Collection(collectionName) 48 | filter := bson.M{"conversationUUID": in.ConversationUUID} 49 | updateFields := bson.M{"$set": bson.M{ 50 | "response": in.Response, 51 | "completionCode": in.CompletionCode, 52 | "updateTime": currentTime, 53 | }} 54 | _, err = collection.UpdateMany(ctx, filter, updateFields) 55 | if err != nil { 56 | return nil, err 57 | } 58 | 59 | return nil, nil 60 | } 61 | -------------------------------------------------------------------------------- /internal/model/input/editin/edit.go: -------------------------------------------------------------------------------- 1 | package editin 2 | 3 | type UserContext struct { 4 | Type string `json:"type" binding:"omitempty"` 5 | Path string `json:"path" binding:"omitempty"` 6 | Content string `json:"content" binding:"omitempty"` 7 | } 8 | 9 | type EditSseGenerateRequest struct { 10 | CreateUser string `json:"createUser" binding:"required"` 11 | ConversationUUID string `json:"conversationUUID" binding:"required"` 12 | // git 信息 13 | GitRepo string `json:"gitRepo" binding:"omitempty"` 14 | GitBranch string `json:"gitBranch" binding:"omitempty"` 15 | // code 信息 16 | CodePath string `json:"codePath" binding:"omitempty"` 17 | CodeLanguage string `json:"codeLanguage" binding:"omitempty"` 18 | // IDE 信息 19 | IdeInfo string `json:"ideInfo" binding:"required"` 20 | ProjectVersion string `json:"projectVersion" binding:"required"` 21 | // edit 信息 22 | UserText string `json:"userText" binding:"omitempty"` 23 | UserCode string `json:"userCode" binding:"required"` 24 | UserContext []*UserContext `json:"userContext" binding:"omitempty"` 25 | } 26 | 27 | type EditSseGenerateResponse struct{} 28 | 29 | type GenerateEditPromptRequest struct { 30 | RepoName string `json:"repoName" binding:"required"` 31 | CodePath string `json:"codePath" binding:"required"` 32 | CodeLanguage string `json:"codeLanguage" binding:"required"` 33 | SelectedCode string `json:"selectCode" binding:"required"` 34 | UserContent string `json:"userContent" binding:"omitempty"` 35 | SharedContexts []*UserContext `json:"sharedContexts" binding:"required"` 36 | } 37 | 38 | type EditAcceptStatusUpdateRequest struct { 39 | ConversationUUID string `json:"conversationUUID" binding:"required"` 40 | AcceptStatus string `json:"acceptStatus" binding:"required"` 41 | } 42 | 43 | type EditAcceptStatusUpdateResponse struct { 44 | ConversationUUID string `json:"conversationUUID" binding:"required"` 45 | } 46 | -------------------------------------------------------------------------------- /core/prompts/chat/chat.go: -------------------------------------------------------------------------------- 1 | package chat 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/gogf/gf/v2/util/gconv" 7 | "xcoder/internal/model/input/chatin" 8 | ) 9 | 10 | const SystemMessage = `你是XCoder,一个由XinYe公司开发的AI编程助手。当被问及你的名字时,你需要回答“XCoder”。 11 | 你需要严格按照用户的要求行事。以下是你的工作方式: 12 | 1. 每个代码块都以 %[1]s 开始。 13 | 2. 你的回答使用 Markdown 格式。 14 | 3. 你的回答尽量简短且仅限于技术领域。 15 | 请注意,你是一个AI编程助手,只对软件开发相关的问题进行回答。` 16 | 17 | const userInstruction = `在回答用户问题时,请遵循以下格式: 18 | 在输出代码之前,首先对代码的目的、逻辑和实现步骤进行清晰的描述。这将帮助用户理解代码的背景和用途。 19 | 使用适当的代码块语法,并在代码块的开头标明使用的编程语言。确保文件名和路径用单引号括起,便于识别。 20 | 在代码部分结束后,使用中文总结性语句,简要概括代码的功能或注意事项。 21 | 注意:如果用户问题中指定了编程语言,则按照用户指定的回答;否则,可以使用 %s 代码语言回答用户问题` 22 | 23 | func GenerateChatPrompt(ctx context.Context, in *chatin.GenerateChatPromptReq) ([]map[string]string, error) { 24 | // 系统消息 25 | tmpls := []map[string]string{ 26 | { 27 | "role": "system", 28 | "content": fmt.Sprintf(SystemMessage, "```", in.CodeLanguage), 29 | }, 30 | } 31 | 32 | // 加入历史聊天记录 33 | idx := len(in.Messages) - 1 34 | lastMsg := in.Messages[idx] 35 | beforeMsgs := in.Messages[:idx] 36 | if len(beforeMsgs) > 0 { 37 | historyStr := gconv.String(beforeMsgs) 38 | historyMsg := map[string]string{ 39 | "role": "user", 40 | "content": fmt.Sprintf("这些是用户聊天记录的会话历史:\n%s\n", historyStr), 41 | } 42 | tmpls = append(tmpls, historyMsg) 43 | } 44 | 45 | // 加入用户选择的代码 46 | if len(in.SelectedCode) > 0 { 47 | selectedMsg := map[string]string{ 48 | "role": "user", 49 | "content": fmt.Sprintf("这个代码片段是用户挑选的代码,希望你针对这段代码进行回答:\n```%s\n%s\n```", 50 | in.CodeLanguage, in.SelectedCode), 51 | } 52 | tmpls = append(tmpls, selectedMsg) 53 | } 54 | 55 | // 加入用户指令 56 | userInstructionMsg := map[string]string{ 57 | "role": "user", 58 | "content": fmt.Sprintf(userInstruction, in.CodeLanguage), 59 | } 60 | tmpls = append(tmpls, userInstructionMsg) 61 | 62 | // 加入用户最新的问题 63 | lastMsgMsg := map[string]string{ 64 | "role": "user", 65 | "content": lastMsg.Content, 66 | } 67 | tmpls = append(tmpls, lastMsgMsg) 68 | 69 | return tmpls, nil 70 | } 71 | -------------------------------------------------------------------------------- /internal/dao/mongodb/code_generate.go: -------------------------------------------------------------------------------- 1 | package mongodb 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "go.mongodb.org/mongo-driver/bson" 7 | "google.golang.org/protobuf/types/known/emptypb" 8 | "time" 9 | "xcoder/internal/model/input/codein" 10 | ) 11 | 12 | func (d *MongodbDao) CodeGenerateInsert(ctx context.Context, in *codein.CodeGenerateInsertMongoRequest) ( 13 | *emptypb.Empty, error) { 14 | client, err := d.GetClient(ctx, d.DbName) 15 | if err != nil { 16 | return nil, err 17 | } 18 | 19 | currentTime := time.Now() 20 | year, month, _ := currentTime.Date() 21 | collectionName := fmt.Sprintf("code_generate_%d_%02d", year, int(month)) 22 | collection := client.Database(d.DbName).Collection(collectionName) 23 | _, err = collection.InsertOne(ctx, bson.M{ 24 | "insertTime": currentTime, 25 | "updateTime": currentTime, 26 | "generateUUID": in.GenerateUUID, 27 | "codeBeforeCursor": in.CodeBeforeCursor, 28 | "codeAfterCursor": in.CodeAfterCursor, 29 | "codeBeforeWithContext": in.CodeBeforeWithContext, 30 | "completionCode": "", 31 | }) 32 | if err != nil { 33 | return nil, err 34 | } 35 | 36 | return nil, nil 37 | } 38 | 39 | func (d *MongodbDao) CodeGenerateUpdate(ctx context.Context, in *codein.CodeGenerateUpdateMongoRequest) ( 40 | *emptypb.Empty, error) { 41 | client, err := d.GetClient(ctx, d.DbName) 42 | if err != nil { 43 | return nil, err 44 | } 45 | 46 | currentTime := time.Now() 47 | year, month, _ := currentTime.Date() 48 | collectionName := fmt.Sprintf("code_generate_%d_%02d", year, int(month)) 49 | collection := client.Database(d.DbName).Collection(collectionName) 50 | filter := bson.M{"generateUUID": in.GenerateUUID} 51 | updateFields := bson.M{"$set": bson.M{ 52 | "completionCode": in.CompletionCode, 53 | "rawCompletionCode": in.RawCompletionCode, 54 | "updateTime": currentTime, 55 | }} 56 | _, err = collection.UpdateMany(ctx, filter, updateFields) 57 | if err != nil { 58 | return nil, err 59 | } 60 | 61 | return nil, nil 62 | } 63 | -------------------------------------------------------------------------------- /internal/logic/common/chat_common_test.go: -------------------------------------------------------------------------------- 1 | package common_test 2 | 3 | import ( 4 | "context" 5 | "github.com/stretchr/testify/assert" 6 | "github.com/stretchr/testify/require" 7 | "testing" 8 | "xcoder/internal/logic/common" 9 | "xcoder/internal/model/input/chatin" 10 | ) 11 | 12 | func TestInitLLM(t *testing.T) { 13 | ctx := context.Background() 14 | 15 | t.Run("successful initialization with OpenAI", func(t *testing.T) { 16 | llmParams := &chatin.ChatLLMParamsConfig{ 17 | Config: &chatin.ChatLLMParams{ 18 | LLMType: "openai", 19 | LLMParams: &chatin.LLMParams{ 20 | APIBase: "https://api.openai.com", 21 | APIKey: "test-api-key", 22 | APIVersion: "v1", 23 | Model: "gpt-3.5-turbo", 24 | }, 25 | }, 26 | } 27 | 28 | llm, err := common.InitLLM(ctx, llmParams) 29 | require.NoError(t, err) 30 | require.NotNil(t, llm) 31 | //assert.Equal(t, openai.APITypeOpenAI, llm.GetAPIType()) 32 | }) 33 | 34 | t.Run("successful initialization with Azure", func(t *testing.T) { 35 | llmParams := &chatin.ChatLLMParamsConfig{ 36 | Config: &chatin.ChatLLMParams{ 37 | LLMType: "azure", 38 | LLMParams: &chatin.LLMParams{ 39 | APIBase: "https://api.openai.com", 40 | APIKey: "test-api-key", 41 | APIVersion: "v1", 42 | Model: "gpt-3.5-turbo", 43 | }, 44 | }, 45 | } 46 | 47 | llm, err := common.InitLLM(ctx, llmParams) 48 | require.NoError(t, err) 49 | require.NotNil(t, llm) 50 | //assert.Equal(t, openai.APITypeAzure, llm.GetAPIType()) 51 | }) 52 | 53 | t.Run("initialization failure", func(t *testing.T) { 54 | llmParams := &chatin.ChatLLMParamsConfig{ 55 | Config: &chatin.ChatLLMParams{ 56 | LLMType: "aopenai", 57 | LLMParams: &chatin.LLMParams{ 58 | APIBase: "https://api.openai.com", 59 | APIKey: "test-api-key", 60 | APIVersion: "v1", 61 | Model: "gpt-3.5-turbo", 62 | }, 63 | }, 64 | } 65 | 66 | llm, err := common.InitLLM(ctx, llmParams) 67 | require.NoError(t, err) 68 | assert.NotNil(t, llm) 69 | }) 70 | } 71 | -------------------------------------------------------------------------------- /utility/xapollo/retrieval.go: -------------------------------------------------------------------------------- 1 | package xapollo 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/fsnotify/fsnotify" 7 | "github.com/gogf/gf/v2/frame/g" 8 | "xcoder/internal/model/input/codein" 9 | ) 10 | 11 | const ( 12 | RetrievalCrossFileContextParamsConfigPath = "xcoder_code_generate_retrieval_cfc_params_conf" 13 | ) 14 | 15 | var RetrievalCrossFileContextParamsConfigInfo = new(RetrievalCrossFileContextParamsConfig) 16 | 17 | type RetrievalCrossFileContextParamsConfig struct { 18 | Config *codein.RetrievalCrossFileContextParams `json:"config"` 19 | } 20 | 21 | func init() { 22 | viperCfg := NewViperConfig() 23 | viperCfg.SetConfigName(RetrievalCrossFileContextParamsConfigPath) 24 | if err := viperCfg.ReadInConfig(); err != nil { 25 | g.Log().Errorf( 26 | context.Background(), 27 | "read config file %s error: %s", RetrievalCrossFileContextParamsConfigPath, 28 | err.Error(), 29 | ) 30 | } 31 | if err := viperCfg.Unmarshal(RetrievalCrossFileContextParamsConfigInfo); err != nil { 32 | g.Log().Errorf( 33 | context.Background(), 34 | "unmarshal config file %s error: %s", RetrievalCrossFileContextParamsConfigPath, 35 | err.Error(), 36 | ) 37 | return 38 | } 39 | g.Log().Infof(context.Background(), "load config file %s success", RetrievalCrossFileContextParamsConfigPath) 40 | 41 | viperCfg.WatchConfig() 42 | viperCfg.OnConfigChange(func(in fsnotify.Event) { 43 | if err := viperCfg.Unmarshal(RetrievalCrossFileContextParamsConfigInfo); err != nil { 44 | g.Log().Errorf( 45 | context.Background(), 46 | "unmarshal config file %s error: %s", RetrievalCrossFileContextParamsConfigPath, 47 | err.Error(), 48 | ) 49 | return 50 | } 51 | g.Log().Infof(context.Background(), "load config file %s success", RetrievalCrossFileContextParamsConfigPath) 52 | }) 53 | } 54 | 55 | func GetRetrievalCrossFileContextParams() (*codein.RetrievalCrossFileContextParams, error) { 56 | if RetrievalCrossFileContextParamsConfigInfo.Config == nil { 57 | return nil, fmt.Errorf("模型 cfc 配置信息为空") 58 | } 59 | 60 | return RetrievalCrossFileContextParamsConfigInfo.Config, nil 61 | } 62 | -------------------------------------------------------------------------------- /hack/hack.mk: -------------------------------------------------------------------------------- 1 | include ./hack/hack-cli.mk 2 | 3 | # Update GoFrame and its CLI to latest stable version. 4 | .PHONY: up 5 | up: cli.install 6 | @gf up -a 7 | 8 | # Build binary using configuration from hack/config.yaml. 9 | .PHONY: build 10 | build: cli.install 11 | @gf build -ew 12 | 13 | # Parse api and generate controller/sdk. 14 | .PHONY: ctrl 15 | ctrl: cli.install 16 | @gf gen ctrl 17 | 18 | # Generate Go files for DAO/DO/Entity. 19 | .PHONY: dao 20 | dao: cli.install 21 | @gf gen dao 22 | 23 | # Parse current project go files and generate enums go file. 24 | .PHONY: enums 25 | enums: cli.install 26 | @gf gen enums 27 | 28 | # Generate Go files for Service. 29 | .PHONY: service 30 | service: cli.install 31 | @gf gen service 32 | 33 | 34 | # Build docker image. 35 | .PHONY: image 36 | image: cli.install 37 | $(eval _TAG = $(shell git describe --dirty --always --tags --abbrev=8 --match 'v*' | sed 's/-/./2' | sed 's/-/./2')) 38 | ifneq (, $(shell git status --porcelain 2>/dev/null)) 39 | $(eval _TAG = $(_TAG).dirty) 40 | endif 41 | $(eval _TAG = $(if ${TAG}, ${TAG}, $(_TAG))) 42 | $(eval _PUSH = $(if ${PUSH}, ${PUSH}, )) 43 | @gf docker ${_PUSH} -tn $(DOCKER_NAME):${_TAG}; 44 | 45 | 46 | # Build docker image and automatically push to docker repo. 47 | .PHONY: image.push 48 | image.push: 49 | @make image PUSH=-p; 50 | 51 | 52 | # Deploy image and yaml to current kubectl environment. 53 | .PHONY: deploy 54 | deploy: 55 | $(eval _TAG = $(if ${TAG}, ${TAG}, develop)) 56 | 57 | @set -e; \ 58 | mkdir -p $(ROOT_DIR)/temp/kustomize;\ 59 | cd $(ROOT_DIR)/manifest/deploy/kustomize/overlays/${_ENV};\ 60 | kustomize build > $(ROOT_DIR)/temp/kustomize.yaml;\ 61 | kubectl apply -f $(ROOT_DIR)/temp/kustomize.yaml; \ 62 | if [ $(DEPLOY_NAME) != "" ]; then \ 63 | kubectl patch -n $(NAMESPACE) deployment/$(DEPLOY_NAME) -p "{\"spec\":{\"template\":{\"metadata\":{\"labels\":{\"date\":\"$(shell date +%s)\"}}}}}"; \ 64 | fi; 65 | 66 | 67 | # Parsing protobuf files and generating go files. 68 | .PHONY: pb 69 | pb: cli.install 70 | @gf gen pb 71 | 72 | # Generate protobuf files for database tables. 73 | .PHONY: pbentity 74 | pbentity: cli.install 75 | @gf gen pbentity -------------------------------------------------------------------------------- /internal/dao/p_chat_records.go: -------------------------------------------------------------------------------- 1 | // ================================================================================= 2 | // This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish. 3 | // ================================================================================= 4 | 5 | package dao 6 | 7 | import ( 8 | "context" 9 | "github.com/gogf/gf/v2/frame/g" 10 | "github.com/gogf/gf/v2/util/gconv" 11 | "xcoder/internal/dao/internal" 12 | "xcoder/internal/model/entity" 13 | ) 14 | 15 | // internalPChatRecordsDao is internal type for wrapping internal DAO implements. 16 | type internalPChatRecordsDao = *internal.PChatRecordsDao 17 | 18 | // pChatRecordsDao is the data access object for table p_chat_records. 19 | // You can define custom methods on it to extend its functionality as you wish. 20 | type pChatRecordsDao struct { 21 | internalPChatRecordsDao 22 | } 23 | 24 | var ( 25 | // PChatRecords is globally public accessible object for table p_chat_records operations. 26 | PChatRecords = pChatRecordsDao{ 27 | internal.NewPChatRecordsDao(), 28 | } 29 | ) 30 | 31 | func (dao *pChatRecordsDao) Create(ctx context.Context, data *entity.PChatRecords) error { 32 | _, err := g.Model(PChatRecords.Table()).Data(data).Insert() 33 | if err != nil { 34 | return err 35 | } 36 | return nil 37 | } 38 | 39 | func (dao *pChatRecordsDao) GetOneByConversationUuid(ctx context.Context, conversationUuid string) ( 40 | result *entity.PChatRecords, err error) { 41 | data, err := g.Model(PChatRecords.Table()).Where(map[string]interface{}{ 42 | "isactive": 1, "conversation_uuid": conversationUuid, 43 | }).One() 44 | if err != nil { 45 | return nil, err 46 | } 47 | 48 | err = gconv.Struct(data, &result) 49 | if err != nil { 50 | return nil, err 51 | } 52 | 53 | return result, nil 54 | } 55 | 56 | func (dao *pChatRecordsDao) UpdateByConversationUuid(ctx context.Context, conversationUuid string, updated map[string]interface{}) error { 57 | _, err := g.Model(PChatRecords.Table()).Where(map[string]interface{}{ 58 | "isactive": 1, "conversation_uuid": conversationUuid, 59 | }).Data(updated).Update() 60 | if err != nil { 61 | return err 62 | } 63 | 64 | return nil 65 | } -------------------------------------------------------------------------------- /manifest/deploy/docker-compose/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | services: 3 | mongodb: 4 | image: mongo:latest 5 | container_name: xcoder_mongodb 6 | privileged: true 7 | restart: always 8 | ports: 9 | - "27017:27017" 10 | volumes: 11 | - ./data/mongo/db:/data/db 12 | - ./data/mongo/log:/var/log/mongodb 13 | - ../initdb.d/init-mongo.sh:/docker-entrypoint-initdb.d/init-mongo.sh:ro 14 | environment: 15 | MONGO_INITDB_ROOT_USERNAME: xcoder_mongo_user 16 | MONGO_INITDB_ROOT_PASSWORD: xcoder_mongo_pwd 17 | MONGO_INITDB_DATABASE: xcoder_mongo_db 18 | networks: 19 | - xcoder_network 20 | healthcheck: 21 | test: [ "CMD", "mongo", "--eval", "db.stats()" ] 22 | interval: 10s 23 | timeout: 5s 24 | retries: 10 25 | 26 | mysql: 27 | image: mysql:8.0 28 | container_name: xcoder_mysql 29 | privileged: true 30 | environment: 31 | MYSQL_ROOT_PASSWORD: xcoder_root_password 32 | MYSQL_DATABASE: xcoder_mysql_db 33 | MYSQL_USER: xcoder_mysql_user 34 | MYSQL_PASSWORD: xcoder_mysql_pwd 35 | TZ: Asia/Shanghai 36 | ports: 37 | - "3306:3306" 38 | volumes: 39 | - ./data/mysql/data:/var/lib/mysql 40 | - ./data/mysql/conf:/etc/mysql/conf.d 41 | - ./data/mysql/log:/var/log/mysql 42 | - ../initdb.d/init_mysql.sql:/docker-entrypoint-initdb.d/init_mysql.sql:ro 43 | networks: 44 | - xcoder_network 45 | healthcheck: 46 | test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] 47 | interval: 10s 48 | timeout: 5s 49 | retries: 5 50 | 51 | xcoder: 52 | build: 53 | context: ../../.. 54 | dockerfile: Dockerfile 55 | image: xcoderai/xcoder:v0.1.0 56 | container_name: xcoder_standalone 57 | privileged: true 58 | environment: 59 | CFG_BASE_DIR: /app/config 60 | ports: 61 | - "8081:8081" 62 | volumes: 63 | - ../../config:/app/config:ro 64 | depends_on: 65 | mongodb: 66 | condition: service_healthy 67 | mysql: 68 | condition: service_healthy 69 | networks: 70 | - xcoder_network 71 | 72 | 73 | networks: 74 | xcoder_network: -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | # XCoder 2 | ## 产品概述 3 | ### 1. XCoder是什么? 4 | XCoder是一款基于大语言模型(LLM)的编码辅助工具,旨在提供智能化的代码推理和辅助功能。 5 | XCoder的开发灵感来源于类似产品 Copilot 的代码自动补全功能,无论是在功能还是性能方面都基于我们公司的现状进行了的优化和改良。 6 | XCoder目前已经过多个内测版本的迭代和用户反馈的持续优化。 7 | ### 2. 核心功能和特点 8 | | 分类 | 支持的语言 | 9 | |----|--------------------------------| 10 | | 后端 | java、golang、python、c++、php ... | 11 | | 前端 | javascript、typescript、html ... | 12 | | 其他 | yaml、markdown、json ... | 13 | 14 | | 功能 | 描述 | 15 | |-----------|----------------------------------------------------| 16 | | 单行、多行代码补全 | 根据用户当前上下文及 IDE 打开的其他文件获取 context ,生成接下来一行或者多行代码的内容 | 17 | | 智能chat | 提供对编码问题的智能回答和解决方案,帮助用户快速解决问题 | 18 | | 单元测试 | 根据所选函数及上下文,智能生成代码的单元测试用例 | 19 | | 代码文档及注释生成 | 根据所选代码,生成代码文档及代码注释 | 20 | | 代码优化 | 智能修改、重构、优化用户所选的代码 | 21 | | 代码解释 | 根据所选代码,生成对代码的功能解释 | 22 | | 代码编辑 | 根据用户给定的指令,进行修改或调整选中代码 | 23 | 24 | ### 3. 优势和价值 25 | 1.提高编码效率:通过智能推理和代码补全功能,加速编码过程,减少重复劳动。 26 | 2.提升代码质量:自动生成单元测试用例和提供代码修复建议,帮助用户编写更可靠和高质量的代码。 27 | 3.加速学习曲线:对于初学者来说,XCoder提供了实时的代码推理和建议,帮助他们理解和学习编码。 28 | 29 | ## 快速开始 30 | 31 | ### XCoder 项目部署 32 | #### Docker 部署 33 | * 安装好 docker 及 docker-compose 34 | * 拉取项目代码 35 | * 模型配置:项目需要使用代码大模型以及通用 chat 大模型,需要自己本地部署好,或使用现有开源的大模型。不管使用那种方式,都需要将模型的连接串信息填写到 `manifest/config/` 目录的配置中,具体可以参考[项目参数配置](./docs/development.md#项目参数配置) 36 | * 进入项目 `manifest/deploy/docker-compose/` 目录,执行 `docker-compose up -d` 37 | 38 | #### 本地构建 39 | * 安装 golang 1.18+ 40 | * 拉取项目代码 41 | * 配置 mysql 及 mongodb 数据库信息,进入 `manifest/config/config.yaml` 配置自己的 mysql, mongodb 连接串信息 42 | * 模型配置:项目需要使用代码大模型以及通用 chat 大模型,需要自己本地部署好,或使用现有开源的大模型。不管使用那种方式,都需要将模型的连接串信息填写到 `manifest/config/` 目录的配置中,具体可以参考[项目参数配置](./docs/development.md#项目参数配置) 43 | * 通过环境变量指定项目配置文件,export CFG_BASE_DIR=manifest/config 44 | * 进入项目根目录,执行 `go mod tidy` 45 | * 进入项目根目录,执行 `go run main.go` 46 | 47 | #### 插件安装部署 48 | 插件安装部署,请参考项目[XCoder-Intellij](https://github.com/FinVolution/XCoder-Intellij)。 49 | 50 | ## 更多配置及部署 51 | 更多项目的配置及部署信息,请参考[开发文档](./docs/development.md) 52 | -------------------------------------------------------------------------------- /internal/dao/p_code_gen_records.go: -------------------------------------------------------------------------------- 1 | // ================================================================================= 2 | // This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish. 3 | // ================================================================================= 4 | 5 | package dao 6 | 7 | import ( 8 | "context" 9 | "github.com/gogf/gf/v2/frame/g" 10 | "github.com/gogf/gf/v2/util/gconv" 11 | "xcoder/internal/dao/internal" 12 | "xcoder/internal/model/entity" 13 | ) 14 | 15 | // internalPCodeGenRecordsDao is internal type for wrapping internal DAO implements. 16 | type internalPCodeGenRecordsDao = *internal.PCodeGenRecordsDao 17 | 18 | // pCodeGenRecordsDao is the data access object for table p_code_gen_records. 19 | // You can define custom methods on it to extend its functionality as you wish. 20 | type pCodeGenRecordsDao struct { 21 | internalPCodeGenRecordsDao 22 | } 23 | 24 | var ( 25 | // PCodeGenRecords is globally public accessible object for table p_code_gen_records operations. 26 | PCodeGenRecords = pCodeGenRecordsDao{ 27 | internal.NewPCodeGenRecordsDao(), 28 | } 29 | ) 30 | 31 | func (dao *pCodeGenRecordsDao) Create(ctx context.Context, data *entity.PCodeGenRecords) error { 32 | _, err := g.Model(PCodeGenRecords.Table()).Data(data).Insert() 33 | if err != nil { 34 | return err 35 | } 36 | return nil 37 | } 38 | 39 | func (dao *pCodeGenRecordsDao) GetOneByGenerateUuid(ctx context.Context, generateUuid string) ( 40 | result *entity.PCodeGenRecords, err error) { 41 | data, err := g.Model(PCodeGenRecords.Table()).Where(map[string]interface{}{ 42 | "isactive": 1, "generate_uuid": generateUuid, 43 | }).One() 44 | if err != nil { 45 | return nil, err 46 | } 47 | 48 | err = gconv.Struct(data, &result) 49 | if err != nil { 50 | return nil, err 51 | } 52 | 53 | return result, nil 54 | } 55 | 56 | func (dao *pCodeGenRecordsDao) UpdateByGenerateUuid(ctx context.Context, generateUuid string, updated map[string]interface{}) error { 57 | _, err := g.Model(PCodeGenRecords.Table()).Where(map[string]interface{}{ 58 | "isactive": 1, "generate_uuid": generateUuid, 59 | }).Data(updated).Update() 60 | if err != nil { 61 | return err 62 | } 63 | 64 | return nil 65 | } 66 | -------------------------------------------------------------------------------- /internal/logic/chat/chat.go: -------------------------------------------------------------------------------- 1 | package chat 2 | 3 | import ( 4 | "context" 5 | "github.com/gogf/gf/v2/frame/g" 6 | "xcoder/core/prompts/chat" 7 | "xcoder/internal/consts" 8 | "xcoder/internal/dao" 9 | "xcoder/internal/logic/common" 10 | "xcoder/internal/model/input/chatin" 11 | "xcoder/internal/service" 12 | "xcoder/utility/xutils" 13 | ) 14 | 15 | type sChat struct{} 16 | 17 | func NewChat() service.IChat { 18 | return &sChat{} 19 | } 20 | 21 | func init() { 22 | service.RegisterChat(NewChat()) 23 | } 24 | 25 | func (s *sChat) SseGenerate(ctx context.Context, in *chatin.ChatSseGenerateReq, out chan *chatin.ChatResult) error { 26 | // 将 code language 转换为小写,并将 go 转换为 golang 27 | codeLanguage := xutils.CodeLanguageToLower(in.CodeLanguage) 28 | 29 | // 组建 prompts 30 | prompts, err := chat.GenerateChatPrompt(ctx, &chatin.GenerateChatPromptReq{ 31 | RepoName: in.GitRepo, 32 | CodePath: in.CodePath, 33 | CodeLanguage: codeLanguage, 34 | Messages: in.Message, 35 | Contexts: in.Context, 36 | SelectedCode: in.UserCode, 37 | }) 38 | 39 | // 调用 Chat LLM 服务 40 | err = common.LLMRun(ctx, &chatin.ChatLLMRunReq{ 41 | Input: in, 42 | Output: out, 43 | ConversationType: consts.ConversationChat.String(), 44 | Prompt: prompts, 45 | PromptVersion: chat.PromptVersion, 46 | }) 47 | if err != nil { 48 | g.Log().Errorf(ctx, "ConversationUUID: %s LLMRun failed: %v", in.ConversationUUID, err) 49 | return err 50 | } 51 | 52 | return nil 53 | } 54 | 55 | func (s *sChat) UpdateChatRecordsFields(ctx context.Context, in *chatin.ChatMessageUpdateMysqlReq) ( 56 | out *chatin.ChatMessageUpdateMysqlRes, err error) { 57 | _, err = dao.PChatRecords.GetOneByConversationUuid(ctx, in.ConversationUUID) 58 | if err != nil { 59 | return nil, err 60 | } 61 | 62 | updatedFields := map[string]interface{}{ 63 | "prompt_tokens": in.PromptTokens, 64 | "completion_tokens": in.CompletionTokens, 65 | "total_tokens": in.TotalTokens, 66 | "completion_duration": in.CompletionDuration, 67 | } 68 | 69 | err = dao.PChatRecords.UpdateByConversationUuid(ctx, in.ConversationUUID, updatedFields) 70 | if err != nil { 71 | return nil, err 72 | } 73 | 74 | return nil, nil 75 | } 76 | -------------------------------------------------------------------------------- /utility/xapollo/code.go: -------------------------------------------------------------------------------- 1 | package xapollo 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/fsnotify/fsnotify" 7 | "github.com/gogf/gf/v2/frame/g" 8 | "xcoder/internal/model/input/codein" 9 | ) 10 | 11 | const ( 12 | CodeGenerateLLMParamsConfigPath = "xcoder_code_generate_llm_params_conf" 13 | ) 14 | 15 | var CodeGenerateLLMParamsConfigInfo = new(CodeGenerateLLMParamsConfig) 16 | 17 | type CodeGenerateLLMParamsConfig struct { 18 | Config map[string]*codein.CodeGenerateLLMParams `json:"config"` 19 | SelectedModel string `json:"selectedModel"` 20 | } 21 | 22 | func init() { 23 | viperCfgC := NewViperConfig() 24 | viperCfgC.SetConfigName(CodeGenerateLLMParamsConfigPath) 25 | if err := viperCfgC.ReadInConfig(); err != nil { 26 | g.Log().Errorf(context.Background(), "read config file %s error: %s", CodeGenerateLLMParamsConfigPath, err.Error()) 27 | } 28 | if err := viperCfgC.Unmarshal(CodeGenerateLLMParamsConfigInfo); err != nil { 29 | g.Log().Errorf(context.Background(), "unmarshal config file %s error: %s", CodeGenerateLLMParamsConfigPath, err.Error()) 30 | return 31 | } 32 | g.Log().Infof(context.Background(), "load config file %s success", CodeGenerateLLMParamsConfigPath) 33 | 34 | viperCfgC.WatchConfig() 35 | viperCfgC.OnConfigChange(func(in fsnotify.Event) { 36 | if err := viperCfgC.Unmarshal(CodeGenerateLLMParamsConfigInfo); err != nil { 37 | g.Log().Errorf(context.Background(), "unmarshal config file %s error: %s", CodeGenerateLLMParamsConfigPath, err.Error()) 38 | return 39 | } 40 | g.Log().Infof(context.Background(), "load config file %s success", CodeGenerateLLMParamsConfigPath) 41 | }) 42 | } 43 | 44 | func GetSelectedModel() string { 45 | return CodeGenerateLLMParamsConfigInfo.SelectedModel 46 | } 47 | 48 | func GetCodeGenerateLLmParams() (*codein.CodeGenerateLLMParams, error) { 49 | selectedModel := GetSelectedModel() 50 | if selectedModel == "" { 51 | return nil, fmt.Errorf("必须选择一个模型使用") 52 | } 53 | if CodeGenerateLLMParamsConfigInfo.Config == nil { 54 | return nil, fmt.Errorf("模型配置信息为空") 55 | } 56 | 57 | if params, ok := CodeGenerateLLMParamsConfigInfo.Config[selectedModel]; ok { 58 | return params, nil 59 | } 60 | 61 | return nil, fmt.Errorf("模型: %s 配置信息不存在", CodeGenerateLLMParamsConfigInfo.SelectedModel) 62 | } 63 | -------------------------------------------------------------------------------- /utility/xmongo/mongodb.go: -------------------------------------------------------------------------------- 1 | package xmongo 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/gogf/gf/v2/frame/g" 7 | "github.com/gogf/gf/v2/os/gctx" 8 | "github.com/gogf/gf/v2/util/gconv" 9 | "go.mongodb.org/mongo-driver/mongo" 10 | "go.mongodb.org/mongo-driver/mongo/options" 11 | "go.mongodb.org/mongo-driver/mongo/readpref" 12 | "sync" 13 | ) 14 | 15 | type Mongodb struct { 16 | DBName string 17 | configs map[string]*Config 18 | db sync.Map 19 | lock sync.Mutex 20 | } 21 | 22 | func NewFromFile() (*Mongodb, error) { 23 | var ( 24 | configs = make(map[string]*Config) 25 | ctx = gctx.New() 26 | ) 27 | mongodbYAML, err := g.Cfg().Get(ctx, "mongodb.default") 28 | if err != nil { 29 | return nil, err 30 | } 31 | 32 | dbName, ok := mongodbYAML.Map()["dbname"].(string) 33 | if !ok { 34 | return nil, fmt.Errorf("dbname is empty") 35 | } 36 | 37 | config := dbConfig{} 38 | err = gconv.Struct(mongodbYAML, &config) 39 | if err != nil { 40 | return nil, err 41 | } 42 | g.Log().Infof(ctx, "mongodb config: %+v", config) 43 | 44 | configs[dbName] = &Config{ 45 | c: &config, 46 | } 47 | 48 | return &Mongodb{ 49 | DBName: dbName, 50 | configs: configs, 51 | }, nil 52 | } 53 | 54 | func (m *Mongodb) GetClient(ctx context.Context, name string) (*mongo.Client, error) { 55 | // check cache 56 | if client, ok := m.db.Load(name); ok { 57 | return client.(*mongo.Client), nil 58 | } 59 | c, ok := m.configs[name] 60 | if !ok { 61 | return nil, fmt.Errorf("%s has no mongodb config", name) 62 | } 63 | 64 | m.lock.Lock() 65 | defer m.lock.Unlock() 66 | // may has cache 67 | if client, ok := m.db.Load(name); ok { 68 | return client.(*mongo.Client), nil 69 | } 70 | 71 | opts := options.Client(). 72 | ApplyURI("mongodb://" + c.c.Hosts). 73 | SetAuth(options.Credential{ 74 | Username: c.c.Username, 75 | Password: c.c.Password, 76 | AuthSource: c.c.AuthSource, 77 | }). 78 | SetReadPreference(readpref.PrimaryPreferred()) 79 | 80 | if c.c.ReplicaSet != "" { 81 | opts.SetReplicaSet(c.c.ReplicaSet) 82 | } 83 | 84 | client, err := mongo.Connect(ctx, opts) 85 | if err != nil { 86 | return nil, err 87 | } 88 | m.db.Store(name, client) 89 | return client, nil 90 | } 91 | 92 | func (m *Mongodb) GetDBName(ctx context.Context) string { 93 | return m.DBName 94 | } 95 | 96 | func (m *Mongodb) Close() { 97 | m.db.Range(func(_, v interface{}) bool { 98 | v.(*mongo.Client).Disconnect(context.Background()) 99 | return true 100 | }) 101 | } 102 | -------------------------------------------------------------------------------- /internal/logic/code/code_test.go: -------------------------------------------------------------------------------- 1 | package code 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | "github.com/stretchr/testify/mock" 10 | "xcoder/core/llms" 11 | "xcoder/internal/model/input/codein" 12 | ) 13 | 14 | // MockLLM is a mock implementation of the xCoderVLLM.LLM interface 15 | type MockLLM struct { 16 | mock.Mock 17 | } 18 | 19 | func (m *MockLLM) Predict(ctx context.Context, prompt string, options ...llms.CallOption) (string, error) { 20 | args := m.Called(ctx, prompt, options) 21 | return args.String(0), args.Error(1) 22 | } 23 | 24 | func TestGenerateWithVLLM(t *testing.T) { 25 | ctx := context.Background() 26 | mockLLM := new(MockLLM) 27 | //xCoderVLLM.New = func(options ...xCoderVLLM.Option) (xCoderVLLM.LLM, error) { 28 | // return mockLLM, nil 29 | //} 30 | 31 | in := &codein.CodeGenerateWithLLMRequest{ 32 | GenerateUUID: "test-uuid", 33 | CodeBeforeCursor: "before cursor", 34 | CodeAfterCursor: "after cursor", 35 | ModelVersion: "v1", 36 | MaxTokens: 100, 37 | Temperature: 0.7, 38 | TopP: 0.9, 39 | StopWords: []string{"stop"}, 40 | IsSingleLine: true, 41 | ConnUrls: []string{"http://localhost"}, 42 | } 43 | 44 | t.Run("successful response", func(t *testing.T) { 45 | mockLLM.On("Predict", ctx, mock.Anything, mock.Anything).Return("completion", nil) 46 | 47 | resp, err := generateWithVLLM(ctx, in) 48 | assert.NoError(t, err) 49 | assert.Equal(t, "completion", resp.Completion) 50 | }) 51 | 52 | t.Run("error in xCoderVLLM.New", func(t *testing.T) { 53 | //xCoderVLLM.New = func(options ...xCoderVLLM.Option) (xCoderVLLM.LLM, error) { 54 | // return nil, errors.New("failed to create LLM") 55 | //} 56 | 57 | resp, err := generateWithVLLM(ctx, in) 58 | assert.Error(t, err) 59 | assert.Nil(t, resp) 60 | }) 61 | 62 | t.Run("error in GenerateCodeLLamaPrompt", func(t *testing.T) { 63 | //code.GenerateCodeLLamaPrompt = func(before, after string) (string, error) { 64 | // return "", errors.New("failed to generate prompt") 65 | //} 66 | 67 | resp, err := generateWithVLLM(ctx, in) 68 | assert.Error(t, err) 69 | assert.Nil(t, resp) 70 | }) 71 | 72 | t.Run("error in Predict", func(t *testing.T) { 73 | //code.GenerateCodeLLamaPrompt = func(before, after string) (string, error) { 74 | // return "prompt", nil 75 | //} 76 | mockLLM.On("Predict", ctx, mock.Anything, mock.Anything).Return("", errors.New("predict failed")) 77 | 78 | resp, err := generateWithVLLM(ctx, in) 79 | assert.Error(t, err) 80 | assert.Nil(t, resp) 81 | }) 82 | } 83 | -------------------------------------------------------------------------------- /core/llms/base.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Travis Cline 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package llms 22 | 23 | import ( 24 | "context" 25 | "xcoder/core/schema" 26 | ) 27 | 28 | type LanguageModel interface { 29 | GeneratePrompt(ctx context.Context, prompts []schema.PromptValue, options ...CallOption) (LLMResult, error) 30 | GetNumTokens(text string) int 31 | } 32 | 33 | type LLM interface { 34 | Call(ctx context.Context, prompt string, options ...CallOption) (string, error) 35 | Generate(ctx context.Context, prompts []string, options ...CallOption) ([]*Generation, error) 36 | } 37 | 38 | // ChatLLM is a langchaingo LLM that can be used for chatting. 39 | type ChatLLM interface { 40 | Call(ctx context.Context, messages []schema.ChatMessage, options ...CallOption) (*schema.AIChatMessage, error) 41 | Generate(ctx context.Context, messages [][]schema.ChatMessage, options ...CallOption) ([]*Generation, error) 42 | } 43 | 44 | type CodeLLM interface { 45 | Predict(ctx context.Context, prompt string, option ...CallOption) (string, error) 46 | PredictDo(ctx context.Context, prompts []string, options ...CallOption) ([]*Generation, error) 47 | } 48 | 49 | type Generation struct { 50 | Text string `json:"text"` 51 | // Message stores the potentially generated message. 52 | Message *schema.AIChatMessage `json:"message"` 53 | GenerationInfo map[string]any `json:"generation_info"` 54 | } 55 | 56 | type LLMResult struct { 57 | Generations []*Generation 58 | LLMOutput map[string]any 59 | } 60 | -------------------------------------------------------------------------------- /internal/logic/comment/comment.go: -------------------------------------------------------------------------------- 1 | package comment 2 | 3 | import ( 4 | "context" 5 | "github.com/gogf/gf/v2/frame/g" 6 | "github.com/gogf/gf/v2/util/gconv" 7 | "xcoder/core/prompts/comment" 8 | "xcoder/core/schema" 9 | "xcoder/internal/consts" 10 | "xcoder/internal/controller/utils/ccode" 11 | "xcoder/internal/controller/utils/cerror" 12 | "xcoder/internal/logic/common" 13 | "xcoder/internal/model/input/chatin" 14 | "xcoder/internal/model/input/commentin" 15 | "xcoder/internal/service" 16 | "xcoder/utility/xutils" 17 | ) 18 | 19 | type sComment struct{} 20 | 21 | func NewComment() service.IComment { 22 | return &sComment{} 23 | } 24 | 25 | func init() { 26 | service.RegisterComment(NewComment()) 27 | } 28 | 29 | func (s *sComment) SseGenerate(ctx context.Context, in *commentin.CodeCommentRequest, out chan *chatin.ChatResult) error { 30 | // 将 code language 转换为小写,并将 go 转换为 golang 31 | codeLanguage := xutils.CodeLanguageToLower(in.CodeLanguage) 32 | 33 | // 组建 prompts 34 | prompts, err := comment.GenerateCommentPrompt(ctx, 35 | &commentin.GenerateCommentPromptRequest{ 36 | RepoName: in.GitRepo, 37 | CodePath: in.CodePath, 38 | CodeLanguage: codeLanguage, 39 | SelectedCode: in.UserCode, 40 | }) 41 | if err != nil { 42 | g.Log().Errorf(ctx, "CommentSse ConversationUUID: %s, explain.GenerateExplainPrompt failed: %v", in.ConversationUUID, err) 43 | return cerror.NewUserError(err, ccode.CodeGeneratePromptError, ccode.CodeGeneratePromptErrorMessage) 44 | } 45 | 46 | var messages []schema.ChatMessageRequest 47 | err = gconv.Struct(prompts, &messages) 48 | if err != nil { 49 | g.Log().Errorf(ctx, "CommentSse ConversationUUID: %s, gconv.Struct failed: %v", in.ConversationUUID, err) 50 | return err 51 | } 52 | 53 | // 调用 Chat LLM 服务 54 | chatInput := &chatin.ChatSseGenerateReq{} 55 | if err = gconv.Struct(in, chatInput); err != nil { 56 | g.Log().Errorf(ctx, "gconv.Struct failed: %v", err) 57 | return err 58 | } 59 | var codeContexts []chatin.Context 60 | if err = gconv.SliceStruct(in.UserContext, &codeContexts); err != nil { 61 | g.Log().Errorf(ctx, "gconv.SliceStruct failed: %v", err) 62 | return err 63 | } 64 | chatInput.Message = messages 65 | chatInput.Context = codeContexts 66 | err = common.LLMRun(ctx, &chatin.ChatLLMRunReq{ 67 | Input: chatInput, 68 | Output: out, 69 | ConversationType: consts.ConversationComment.String(), 70 | Prompt: prompts, 71 | PromptVersion: comment.PromptVersion, 72 | }) 73 | if err != nil { 74 | g.Log().Errorf(ctx, "ConversationUUID: %s LLMRun failed: %v", in.ConversationUUID, err) 75 | return err 76 | } 77 | 78 | return nil 79 | } 80 | -------------------------------------------------------------------------------- /internal/logic/explain/explain.go: -------------------------------------------------------------------------------- 1 | package explain 2 | 3 | import ( 4 | "context" 5 | "github.com/gogf/gf/v2/frame/g" 6 | "github.com/gogf/gf/v2/util/gconv" 7 | "xcoder/core/prompts/explain" 8 | "xcoder/core/schema" 9 | "xcoder/internal/consts" 10 | "xcoder/internal/controller/utils/ccode" 11 | "xcoder/internal/controller/utils/cerror" 12 | "xcoder/internal/logic/common" 13 | "xcoder/internal/model/input/chatin" 14 | "xcoder/internal/model/input/explainin" 15 | "xcoder/internal/service" 16 | "xcoder/utility/xutils" 17 | ) 18 | 19 | type sExplain struct{} 20 | 21 | func NewExplain() service.IExplain { 22 | return &sExplain{} 23 | } 24 | 25 | func init() { 26 | service.RegisterExplain(NewExplain()) 27 | } 28 | 29 | func (s *sExplain) SseGenerate(ctx context.Context, in *explainin.ExplainSseGenerateRequest, out chan *chatin.ChatResult) error { 30 | // 将 code language 转换为小写,并将 go 转换为 golang 31 | codeLanguage := xutils.CodeLanguageToLower(in.CodeLanguage) 32 | 33 | // 组建 prompts 34 | prompts, err := explain.GenerateExplainPrompt(ctx, 35 | &explainin.CodeExplainPromptRequest{ 36 | RepoName: in.GitRepo, 37 | CodePath: in.CodePath, 38 | CodeLanguage: codeLanguage, 39 | SelectedCode: in.UserCode, 40 | }) 41 | if err != nil { 42 | g.Log().Errorf(ctx, "ConversationUUID: %s, explain.GenerateExplainPrompt failed: %v", 43 | in.ConversationUUID, err) 44 | return cerror.NewUserError(err, ccode.CodeGeneratePromptError, 45 | ccode.CodeGeneratePromptErrorMessage) 46 | } 47 | 48 | var messages []schema.ChatMessageRequest 49 | err = gconv.Struct(prompts, &messages) 50 | if err != nil { 51 | g.Log().Errorf(ctx, "ConversationUUID: %s, gconv.Struct failed: %v", in.ConversationUUID, err) 52 | return err 53 | } 54 | 55 | // 调用 Chat LLM 服务 56 | chatInput := &chatin.ChatSseGenerateReq{} 57 | if err = gconv.Struct(in, chatInput); err != nil { 58 | g.Log().Errorf(ctx, "gconv.Struct failed: %v", err) 59 | return err 60 | } 61 | var codeContexts []chatin.Context 62 | if err = gconv.SliceStruct(in.UserContext, &codeContexts); err != nil { 63 | g.Log().Errorf(ctx, "gconv.SliceStruct failed: %v", err) 64 | return err 65 | } 66 | chatInput.Message = messages 67 | chatInput.Context = codeContexts 68 | err = common.LLMRun(ctx, &chatin.ChatLLMRunReq{ 69 | Input: chatInput, 70 | Output: out, 71 | ConversationType: consts.ConversationExplain.String(), 72 | Prompt: prompts, 73 | PromptVersion: explain.PromptVersion, 74 | }) 75 | if err != nil { 76 | g.Log().Errorf(ctx, "ConversationUUID: %s LLMRun failed: %v", in.ConversationUUID, err) 77 | return err 78 | } 79 | 80 | return nil 81 | } 82 | -------------------------------------------------------------------------------- /internal/logic/edit/edit.go: -------------------------------------------------------------------------------- 1 | package edit 2 | 3 | import ( 4 | "context" 5 | "github.com/gogf/gf/v2/frame/g" 6 | "github.com/gogf/gf/v2/util/gconv" 7 | "xcoder/core/prompts/edit" 8 | "xcoder/core/schema" 9 | "xcoder/internal/consts" 10 | "xcoder/internal/controller/utils/ccode" 11 | "xcoder/internal/controller/utils/cerror" 12 | "xcoder/internal/logic/common" 13 | "xcoder/internal/model/input/chatin" 14 | "xcoder/internal/model/input/editin" 15 | "xcoder/internal/service" 16 | "xcoder/utility/xutils" 17 | ) 18 | 19 | type sEdit struct{} 20 | 21 | func NewEdit() service.IEdit { 22 | return &sEdit{} 23 | } 24 | 25 | func init() { 26 | service.RegisterEdit(NewEdit()) 27 | } 28 | 29 | func (s *sEdit) SseGenerate(ctx context.Context, in *editin.EditSseGenerateRequest, out chan *chatin.ChatResult) error { 30 | // 将 code language 转换为小写,并将 go 转换为 golang 31 | codeLanguage := xutils.CodeLanguageToLower(in.CodeLanguage) 32 | 33 | // 组建 prompts 34 | prompts, err := edit.GenerateEditPrompt(ctx, 35 | &editin.GenerateEditPromptRequest{ 36 | RepoName: in.GitRepo, 37 | CodePath: in.CodePath, 38 | CodeLanguage: codeLanguage, 39 | SelectedCode: in.UserCode, 40 | UserContent: in.UserText, 41 | SharedContexts: in.UserContext, 42 | }) 43 | if err != nil { 44 | g.Log().Errorf(ctx, "ConversationUUID: %s, explain.GenerateExplainPrompt failed: %v", in.ConversationUUID, err) 45 | return cerror.NewUserError(err, ccode.CodeGeneratePromptError, ccode.CodeGeneratePromptErrorMessage) 46 | } 47 | 48 | var messages []schema.ChatMessageRequest 49 | err = gconv.Struct(prompts, &messages) 50 | if err != nil { 51 | g.Log().Errorf(ctx, "ConversationUUID: %s, gconv.Struct failed: %v", in.ConversationUUID, err) 52 | return err 53 | } 54 | 55 | // 调用 Chat LLM 服务 56 | chatInput := &chatin.ChatSseGenerateReq{} 57 | if err = gconv.Struct(in, chatInput); err != nil { 58 | g.Log().Errorf(ctx, "gconv.Struct failed: %v", err) 59 | return err 60 | } 61 | var codeContexts []chatin.Context 62 | if err = gconv.SliceStruct(in.UserContext, &codeContexts); err != nil { 63 | g.Log().Errorf(ctx, "gconv.SliceStruct failed: %v", err) 64 | return err 65 | } 66 | chatInput.Message = messages 67 | chatInput.Context = codeContexts 68 | err = common.LLMRun(ctx, &chatin.ChatLLMRunReq{ 69 | Input: chatInput, 70 | Output: out, 71 | ConversationType: consts.ConversationEdit.String(), 72 | Prompt: prompts, 73 | PromptVersion: edit.PromptVersion, 74 | }) 75 | if err != nil { 76 | g.Log().Errorf(ctx, "ConversationUUID: %s LLMRun failed: %v", in.ConversationUUID, err) 77 | return err 78 | } 79 | 80 | return nil 81 | } 82 | -------------------------------------------------------------------------------- /internal/logic/optimize/optimize.go: -------------------------------------------------------------------------------- 1 | package optimize 2 | 3 | import ( 4 | "context" 5 | "github.com/gogf/gf/v2/frame/g" 6 | "github.com/gogf/gf/v2/util/gconv" 7 | "xcoder/core/prompts/optimize" 8 | "xcoder/core/schema" 9 | "xcoder/internal/consts" 10 | "xcoder/internal/controller/utils/ccode" 11 | "xcoder/internal/controller/utils/cerror" 12 | "xcoder/internal/logic/common" 13 | "xcoder/internal/model/input/chatin" 14 | "xcoder/internal/model/input/optimizein" 15 | "xcoder/internal/service" 16 | "xcoder/utility/xutils" 17 | ) 18 | 19 | type sOptimize struct{} 20 | 21 | func NewOptimize() service.IOptimize { 22 | return &sOptimize{} 23 | } 24 | 25 | func init() { 26 | service.RegisterOptimize(NewOptimize()) 27 | } 28 | 29 | func (s *sOptimize) SseGenerate(ctx context.Context, in *optimizein.OptimizeSseGenerateRequest, out chan *chatin.ChatResult) error { 30 | // 将 code language 转换为小写,并将 go 转换为 golang 31 | codeLanguage := xutils.CodeLanguageToLower(in.CodeLanguage) 32 | 33 | // 组建 prompts 34 | prompts, err := optimize.GenerateOptimizePrompt(ctx, 35 | &optimizein.GenerateCodeOptimizePromptRequest{ 36 | RepoName: in.GitRepo, 37 | CodePath: in.CodePath, 38 | CodeLanguage: codeLanguage, 39 | SelectedCode: in.UserCode, 40 | }) 41 | if err != nil { 42 | g.Log().Errorf(ctx, "ConversationUUID: %s prompts.GenerateCodeOptimizePrompt failed: %v", 43 | in.ConversationUUID, err) 44 | return cerror.NewUserError(err, ccode.CodeGeneratePromptError, 45 | ccode.CodeGeneratePromptErrorMessage) 46 | } 47 | 48 | var messages []schema.ChatMessageRequest 49 | err = gconv.Struct(prompts, &messages) 50 | if err != nil { 51 | g.Log().Errorf(ctx, "ConversationUUID: %s gconv.Struct failed: %v", in.ConversationUUID, err) 52 | return err 53 | } 54 | 55 | // 调用 Chat LLM 服务 56 | chatInput := &chatin.ChatSseGenerateReq{} 57 | if err = gconv.Struct(in, chatInput); err != nil { 58 | g.Log().Errorf(ctx, "gconv.Struct failed: %v", err) 59 | return err 60 | } 61 | var codeContexts []chatin.Context 62 | if err = gconv.SliceStruct(in.UserContext, &codeContexts); err != nil { 63 | g.Log().Errorf(ctx, "gconv.SliceStruct failed: %v", err) 64 | return err 65 | } 66 | chatInput.Message = messages 67 | chatInput.Context = codeContexts 68 | err = common.LLMRun(ctx, &chatin.ChatLLMRunReq{ 69 | Input: chatInput, 70 | Output: out, 71 | ConversationType: consts.ConversationOptimize.String(), 72 | Prompt: prompts, 73 | PromptVersion: optimize.PromptVersion, 74 | }) 75 | if err != nil { 76 | g.Log().Errorf(ctx, "ConversationUUID: %s LLMRun failed: %v", in.ConversationUUID, err) 77 | return err 78 | } 79 | 80 | return nil 81 | } 82 | -------------------------------------------------------------------------------- /internal/logic/unit_test/unit.go: -------------------------------------------------------------------------------- 1 | package unit_test 2 | 3 | import ( 4 | "context" 5 | "github.com/gogf/gf/v2/frame/g" 6 | "github.com/gogf/gf/v2/util/gconv" 7 | "xcoder/core/prompts/unit_test" 8 | "xcoder/core/schema" 9 | "xcoder/internal/consts" 10 | "xcoder/internal/controller/utils/ccode" 11 | "xcoder/internal/controller/utils/cerror" 12 | "xcoder/internal/logic/common" 13 | "xcoder/internal/model/input/chatin" 14 | "xcoder/internal/model/input/unit_testin" 15 | "xcoder/internal/service" 16 | "xcoder/utility/xutils" 17 | ) 18 | 19 | type sUnit struct{} 20 | 21 | func NewUnit() service.IUnit { 22 | return &sUnit{} 23 | } 24 | 25 | func init() { 26 | service.RegisterUnit(NewUnit()) 27 | } 28 | 29 | func (s *sUnit) SseGenerate(ctx context.Context, in *unit_testin.UnitTestSseGenerateReq, out chan *chatin.ChatResult) error { 30 | // 将 code language 转换为小写,并将 go 转换为 golang 31 | codeLanguage := xutils.CodeLanguageToLower(in.CodeLanguage) 32 | 33 | // 组建 prompt 34 | prompts, err := unit_test.GenerateUnitTestPrompt(ctx, 35 | &unit_testin.GenerateUTPromptReq{ 36 | RepoName: in.GitRepo, 37 | CodePath: in.CodePath, 38 | CodeLanguage: codeLanguage, 39 | SharedContexts: in.UserContext, 40 | SelectedCode: in.UserCode, 41 | UserInstruction: in.UserText, 42 | Framework: in.Framework, 43 | }) 44 | if err != nil { 45 | g.Log().Errorf(ctx, "ConversationUUID: %s prompts.GenerateUTPrompt failed: %v", in.ConversationUUID, err) 46 | return cerror.NewUserError(err, ccode.CodeGeneratePromptError, 47 | ccode.CodeGeneratePromptErrorMessage) 48 | } 49 | 50 | var messages []schema.ChatMessageRequest 51 | err = gconv.Struct(prompts, &messages) 52 | if err != nil { 53 | g.Log().Errorf(ctx, "ConversationUUID: %s gconv.Struct failed: %v", in.ConversationUUID, err) 54 | return err 55 | } 56 | 57 | // 调用 Chat LLM 服务 58 | chatInput := &chatin.ChatSseGenerateReq{} 59 | if err = gconv.Struct(in, chatInput); err != nil { 60 | g.Log().Errorf(ctx, "gconv.Struct failed: %v", err) 61 | return err 62 | } 63 | var codeContexts []chatin.Context 64 | if err = gconv.SliceStruct(in.UserContext, &codeContexts); err != nil { 65 | g.Log().Errorf(ctx, "gconv.SliceStruct failed: %v", err) 66 | return err 67 | } 68 | chatInput.Message = messages 69 | chatInput.Context = codeContexts 70 | err = common.LLMRun(ctx, &chatin.ChatLLMRunReq{ 71 | Input: chatInput, 72 | Output: out, 73 | ConversationType: consts.ConversationUT.String(), 74 | Prompt: prompts, 75 | PromptVersion: unit_test.PromptVersion, 76 | }) 77 | if err != nil { 78 | g.Log().Errorf(ctx, "ConversationUUID: %s LLMRun failed: %v", in.ConversationUUID, err) 79 | return err 80 | } 81 | 82 | return nil 83 | } 84 | -------------------------------------------------------------------------------- /core/retrieval/retrieval_test.go: -------------------------------------------------------------------------------- 1 | // FILEPATH: /core/retrieval/retrieval_test.go 2 | 3 | package retrieval 4 | 5 | import ( 6 | "context" 7 | "github.com/stretchr/testify/assert" 8 | "testing" 9 | ) 10 | 11 | func TestMatcher_Retrieval(t *testing.T) { 12 | matcher := NewMatcher("golang", "/path/to/code", 3, 2, 0.5, "query document content") 13 | 14 | tests := []struct { 15 | name string 16 | ctxDoc string 17 | expected string 18 | score float64 19 | wantErr bool 20 | }{ 21 | { 22 | name: "basic functionality", 23 | ctxDoc: `package main\nfunc main() {\nprintln("Hello, World!")\n}`, 24 | expected: `package main\nfunc main() {\nprintln("Hello, World!")\n}`, 25 | score: 0.5, 26 | wantErr: false, 27 | }, 28 | { 29 | name: "empty document", 30 | ctxDoc: "", 31 | expected: "", 32 | score: 0, 33 | wantErr: false, 34 | }, 35 | { 36 | name: "no matching snippet", 37 | ctxDoc: `package main\nfunc main() {\nprintln("No Match")\n}`, 38 | expected: "", 39 | score: 0, 40 | wantErr: false, 41 | }, 42 | } 43 | 44 | for _, tt := range tests { 45 | t.Run(tt.name, func(t *testing.T) { 46 | snippet, score, err := matcher.Retrieval(context.Background(), tt.ctxDoc) 47 | if (err != nil) != tt.wantErr { 48 | t.Errorf("Matcher.Retrieval() error = %v, wantErr %v", err, tt.wantErr) 49 | return 50 | } 51 | assert.Equal(t, tt.expected, snippet) 52 | assert.Equal(t, tt.score, score) 53 | }) 54 | } 55 | } 56 | 57 | func TestMatcher_calJaccardSimilarityScore(t *testing.T) { 58 | tests := []struct { 59 | name string 60 | words1 []string 61 | set2 map[string]bool 62 | want float64 63 | }{ 64 | { 65 | name: "basic functionality", 66 | words1: []string{"hello", "world"}, 67 | set2: map[string]bool{"hello": true, "world": true}, 68 | want: 1.0, 69 | }, 70 | { 71 | name: "partial match", 72 | words1: []string{"hello", "world"}, 73 | set2: map[string]bool{"hello": true, "golang": true}, 74 | want: 0.3333333333333333, 75 | }, 76 | { 77 | name: "no match", 78 | words1: []string{"hello", "world"}, 79 | set2: map[string]bool{"golang": true, "programming": true}, 80 | want: 0.0, 81 | }, 82 | { 83 | name: "empty words1", 84 | words1: []string{}, 85 | set2: map[string]bool{"hello": true, "world": true}, 86 | want: 0.0, 87 | }, 88 | { 89 | name: "empty set2", 90 | words1: []string{"hello", "world"}, 91 | set2: map[string]bool{}, 92 | want: 0.0, 93 | }, 94 | { 95 | name: "both empty", 96 | words1: []string{}, 97 | set2: map[string]bool{}, 98 | want: 0.0, 99 | }, 100 | } 101 | 102 | for _, tt := range tests { 103 | t.Run(tt.name, func(t *testing.T) { 104 | m := &Matcher{} 105 | got := m.calJaccardSimilarityScore(tt.words1, tt.set2) 106 | assert.Equal(t, tt.want, got) 107 | }) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /internal/controller/unit_test/unit_v1_test.go: -------------------------------------------------------------------------------- 1 | package unit_test_test 2 | 3 | import ( 4 | "context" 5 | _ "github.com/gogf/gf/v2/frame/g" 6 | "github.com/stretchr/testify/mock" 7 | "xcoder/internal/model/input/chatin" 8 | ) 9 | 10 | // MockService is a mock implementation of the service.Chat interface 11 | type MockService struct { 12 | mock.Mock 13 | } 14 | 15 | func (m *MockService) SseGenerate(ctx context.Context, req *chatin.ChatSseGenerateReq, resp chan<- *chatin.ChatResult) error { 16 | args := m.Called(ctx, req, resp) 17 | return args.Error(0) 18 | } 19 | 20 | //func TestControllerV1_SseGenerate(t *testing.T) { 21 | // mockService := new(MockService) 22 | // service.Chat = func() service.Chat { 23 | // return mockService 24 | // } 25 | // 26 | // controller := NewV1() 27 | // ctx := context.Background() 28 | // req := &v1.ChatSseGenerateReq{} 29 | // 30 | // t.Run("successful response", func(t *testing.T) { 31 | // respChan := make(chan *chatin.ChatResult, 1) 32 | // mockService.On("SseGenerate", ctx, mock.Anything, mock.Anything).Run(func(args mock.Arguments) { 33 | // resp := args.Get(2).(chan<- *chatin.ChatResult) 34 | // resp <- &chatin.ChatResult{Data: "success"} 35 | // }).Return(nil) 36 | // 37 | // common.GetStreamingChatReq = func(ctx context.Context) *common.StreamingChatReq { 38 | // return &common.StreamingChatReq{} 39 | // } 40 | // 41 | // common.ParseStreamingChatResp = func(ctx context.Context, r *common.StreamingChatReq, resp <-chan *chatin.ChatResult) { 42 | // result := <-resp 43 | // assert.Equal(t, "success", result.Data) 44 | // } 45 | // 46 | // _, err := controller.SseGenerate(ctx, req) 47 | // assert.NoError(t, err) 48 | // }) 49 | // 50 | // t.Run("error in SseGenerate", func(t *testing.T) { 51 | // respChan := make(chan *chatin.ChatResult, 1) 52 | // mockService.On("SseGenerate", ctx, mock.Anything, mock.Anything).Return(assert.AnError) 53 | // 54 | // common.GetStreamingChatReq = func(ctx context.Context) *common.StreamingChatReq { 55 | // return &common.StreamingChatReq{} 56 | // } 57 | // 58 | // common.ParseStreamingChatResp = func(ctx context.Context, r *common.StreamingChatReq, resp <-chan *chatin.ChatResult) { 59 | // result := <-resp 60 | // assert.Error(t, result.Error) 61 | // } 62 | // 63 | // _, err := controller.SseGenerate(ctx, req) 64 | // assert.NoError(t, err) 65 | // }) 66 | // 67 | // t.Run("panic recovery", func(t *testing.T) { 68 | // respChan := make(chan *chatin.ChatResult, 1) 69 | // mockService.On("SseGenerate", ctx, mock.Anything, mock.Anything).Run(func(args mock.Arguments) { 70 | // panic("test panic") 71 | // }).Return(nil) 72 | // 73 | // common.GetStreamingChatReq = func(ctx context.Context) *common.StreamingChatReq { 74 | // return &common.StreamingChatReq{} 75 | // } 76 | // 77 | // common.ParseStreamingChatResp = func(ctx context.Context, r *common.StreamingChatReq, resp <-chan *chatin.ChatResult) { 78 | // select { 79 | // case <-resp: 80 | // case <-time.After(1 * time.Second): 81 | // t.Fatal("timeout waiting for response") 82 | // } 83 | // } 84 | // 85 | // _, err := controller.SseGenerate(ctx, req) 86 | // assert.NoError(t, err) 87 | // }) 88 | //} 89 | -------------------------------------------------------------------------------- /internal/model/entity/p_chat_records.go: -------------------------------------------------------------------------------- 1 | // ================================================================================= 2 | // Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. 3 | // ================================================================================= 4 | 5 | package entity 6 | 7 | import ( 8 | "github.com/gogf/gf/v2/os/gtime" 9 | ) 10 | 11 | // PChatRecords is the golang structure for table p_chat_records. 12 | type PChatRecords struct { 13 | Id uint `json:"id" orm:"id" ` // 序号 14 | ConversationUuid string `json:"conversationUuid" orm:"conversation_uuid" ` // 会话 uuid 15 | CreateUser string `json:"createUser" orm:"create_user" ` // 创建人 16 | GitRepo string `json:"gitRepo" orm:"git_repo" ` // git 仓库 17 | GitBranch string `json:"gitBranch" orm:"git_branch" ` // git 分支 18 | CodePath string `json:"codePath" orm:"code_path" ` // 代码路径 19 | CodeLanguage string `json:"codeLanguage" orm:"code_language" ` // 代码语言 20 | IdeInfo string `json:"ideInfo" orm:"ide_info" ` // ide 版本信息 21 | ProjectName string `json:"projectName" orm:"project_name" ` // 插件名称 22 | ProjectVersion string `json:"projectVersion" orm:"project_version" ` // 插件版本 23 | EngineName string `json:"engineName" orm:"engine_name" ` // 模型名称 24 | ModelName string `json:"modelName" orm:"model_name" ` // 模型名称 25 | ModelVersion string `json:"modelVersion" orm:"model_version" ` // 模型版本 26 | PromptTokens uint `json:"promptTokens" orm:"prompt_tokens" ` // prompt tokens 27 | CompletionTokens uint `json:"completionTokens" orm:"completion_tokens" ` // 生成代码 tokens 28 | TotalTokens uint `json:"totalTokens" orm:"total_tokens" ` // 总 tokens 29 | CompletionCodeLines uint `json:"completionCodeLines" orm:"completion_code_lines" ` // 生成代码行数 30 | CompletionDuration uint `json:"completionDuration" orm:"completion_duration" ` // 生成代码耗时 31 | FailureReason string `json:"failureReason" orm:"failure_reason" ` // 会话失败原因 32 | AcceptStatus string `json:"acceptStatus" orm:"accept_status" ` // 会话是否被采纳 33 | Updatetime *gtime.Time `json:"updatetime" orm:"updatetime" ` // 更新时间 34 | Inserttime *gtime.Time `json:"inserttime" orm:"inserttime" ` // 插入时间 35 | Isactive int `json:"isactive" orm:"isactive" ` // 逻辑删除 36 | DeleteToken string `json:"deleteToken" orm:"delete_token" ` // 逻辑删除标志 37 | ConversationType string `json:"conversationType" orm:"conversation_type" ` // 会话类型 38 | PromptTmplVersion string `json:"promptTmplVersion" orm:"prompt_tmpl_version" ` // prompt 版本 39 | PromptTmplContent string `json:"promptTmplContent" orm:"prompt_tmpl_content" ` // prompt 内容 40 | } 41 | -------------------------------------------------------------------------------- /core/llms/openai/llm.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Travis Cline 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package openai 22 | 23 | import ( 24 | "errors" 25 | "net/http" 26 | "os" 27 | "xcoder/core/llms/openai/internal/openaiclient" 28 | ) 29 | 30 | var ( 31 | ErrEmptyResponse = errors.New("no response") 32 | ErrMissingToken = errors.New("missing the OpenAI API key, set it in the OPENAI_API_KEY environment variable") //nolint:lll 33 | ErrMissingAzureModel = errors.New("model needs to be provided when using Azure API") 34 | ErrMissingAzureEmbeddingModel = errors.New("embeddings model needs to be provided when using Azure API") 35 | ) 36 | 37 | // newClient is wrapper for openaiclient internal package. 38 | func newClient(opts ...Option) (*options, *openaiclient.Client, error) { 39 | options := &options{ 40 | token: os.Getenv(tokenEnvVarName), 41 | model: os.Getenv(modelEnvVarName), 42 | baseURL: getEnvs(baseURLEnvVarName, baseAPIBaseEnvVarName), 43 | organization: os.Getenv(organizationEnvVarName), 44 | apiType: APIType(openaiclient.APITypeOpenAI), 45 | httpClient: http.DefaultClient, 46 | } 47 | 48 | for _, opt := range opts { 49 | opt(options) 50 | } 51 | 52 | // set of options needed for Azure client 53 | if openaiclient.IsAzure(openaiclient.APIType(options.apiType)) && options.apiVersion == "" { 54 | options.apiVersion = DefaultAPIVersion 55 | if options.model == "" { 56 | return options, nil, ErrMissingAzureModel 57 | } 58 | if options.embeddingModel == "" { 59 | return options, nil, ErrMissingAzureEmbeddingModel 60 | } 61 | } 62 | 63 | if len(options.token) == 0 { 64 | return options, nil, ErrMissingToken 65 | } 66 | 67 | cli, err := openaiclient.New(options.token, options.model, options.baseURL, options.organization, 68 | openaiclient.APIType(options.apiType), options.apiVersion, options.httpClient, options.embeddingModel) 69 | return options, cli, err 70 | } 71 | 72 | func getEnvs(keys ...string) string { 73 | for _, key := range keys { 74 | val, ok := os.LookupEnv(key) 75 | if ok { 76 | return val 77 | } 78 | } 79 | return "" 80 | } 81 | -------------------------------------------------------------------------------- /internal/model/entity/p_code_gen_records.go: -------------------------------------------------------------------------------- 1 | // ================================================================================= 2 | // Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. 3 | // ================================================================================= 4 | 5 | package entity 6 | 7 | import ( 8 | "github.com/gogf/gf/v2/os/gtime" 9 | ) 10 | 11 | // PCodeGenRecords is the golang structure for table p_code_gen_records. 12 | type PCodeGenRecords struct { 13 | Id uint `json:"id" orm:"id" ` // 14 | GenerateUuid string `json:"generateUuid" orm:"generate_uuid" ` // 15 | GenerateType string `json:"generateType" orm:"generate_type" ` // 16 | IsSingleLine int `json:"isSingleLine" orm:"is_single_line" ` // 17 | CreateUser string `json:"createUser" orm:"create_user" ` // 18 | GitRepo string `json:"gitRepo" orm:"git_repo" ` // 19 | GitBranch string `json:"gitBranch" orm:"git_branch" ` // 20 | CodePath string `json:"codePath" orm:"code_path" ` // 21 | CodeLanguage string `json:"codeLanguage" orm:"code_language" ` // 22 | IdeInfo string `json:"ideInfo" orm:"ide_info" ` // 23 | StartCursorIdx uint `json:"startCursorIdx" orm:"start_cursor_idx" ` // 24 | PrefixCodeTokens uint `json:"prefixCodeTokens" orm:"prefix_code_tokens" ` // 25 | SuffixCodeTokens uint `json:"suffixCodeTokens" orm:"suffix_code_tokens" ` // 26 | ModelName string `json:"modelName" orm:"model_name" ` // 27 | ModelVersion string `json:"modelVersion" orm:"model_version" ` // 28 | PromptTokens uint `json:"promptTokens" orm:"prompt_tokens" ` // 29 | CompletionCode string `json:"completionCode" orm:"completion_code" ` // 30 | CompletionDuration uint `json:"completionDuration" orm:"completion_duration" ` // 31 | CompletionCodeTokens uint `json:"completionCodeTokens" orm:"completion_code_tokens" ` // 32 | CompletionCodeLines uint `json:"completionCodeLines" orm:"completion_code_lines" ` // 33 | FinishReason string `json:"finishReason" orm:"finish_reason" ` // 34 | FailureReason string `json:"failureReason" orm:"failure_reason" ` // 35 | Updatetime *gtime.Time `json:"updatetime" orm:"updatetime" ` // 36 | Inserttime *gtime.Time `json:"inserttime" orm:"inserttime" ` // 37 | Isactive int `json:"isactive" orm:"isactive" ` // 38 | DeleteToken string `json:"deleteToken" orm:"delete_token" ` // 39 | AcceptStatus string `json:"acceptStatus" orm:"accept_status" ` // 40 | CodeTotalLines int `json:"codeTotalLines" orm:"code_total_lines" ` // 41 | CrossfileCtxNums int `json:"crossfileCtxNums" orm:"crossfile_ctx_nums" ` // 42 | } 43 | -------------------------------------------------------------------------------- /core/prompts/unit_test/unit.go: -------------------------------------------------------------------------------- 1 | package unit_test 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "xcoder/internal/consts" 7 | "xcoder/internal/model/input/unit_testin" 8 | "xcoder/utility/xutils" 9 | ) 10 | 11 | const SystemMessage = `你是XCoder,一个由XinYe公司开发的AI编程助手。当被问及你的名字时,你需要回答“XCoder”。 12 | 你需要严格按照用户的要求行事。以下是你的工作方式: 13 | 1. 每个代码块都以%[1]s开始。 14 | 2. 你总是用%[2]s语言回答。 15 | 3. 当用户要求你提供代码时,你需要以%[2]s代码块的形式回答。 16 | 4. 在答案中使用 Markdown 格式。 17 | 5. 你的答案尽量简短且仅限于技术领域。 18 | 请注意,你是一个AI编程助手,只对软件开发相关的问题进行回答。` 19 | 20 | const UserInstruction = `请为用户挑选的代码生成单元测试,生成过程严格遵循下面的每一个要求: 21 | - 以上有一些用户给定参考的代码片段。包括但不限于有以下几类: 22 | 1. 用户选择的代码所在的上下文文件; 2. 用户选择的代码对应的现有单元测试文件;3. 其他示例的单元测试文件; 23 | - 每个代码片段都是以 %[1]s 以及 %[2]s FILEPATH 开始的。 24 | - 需要符合一下几点单元测试的编写规范: 25 | 1. 使用简单而完整的断言来验证关键功能;2. 无论何时何地运行测试,结果都应该是一致的;3. 测试应该是独立的,不应该依赖于任何外部状态。 26 | - 如果用户提供的参考单元测试文件中,存在对用户挑选的代码的测试,则需要在测试中添加其他新的测试,而不是修改现有的测试。 27 | - 新的测试应该验证预期的功能,并涵盖用户代码的所有必需导入的边缘情况,包括导入被测试的函数。不要重复现有的测试。 28 | - 新的测试应该放置在一个单独的 markdown %[1]s 代码块中。 29 | - 如果 %[3]s 不为空,则使用 %[3]s 框架的单元测试规范。 30 | - 如果 %[3]s 为空,则使用符合 %[1]s 语言框架的单元测试规范。 31 | - 使用中文进行总结测试的覆盖范围以及局限性。%[4]s` 32 | 33 | func GenerateUnitTestPrompt(ctx context.Context, in *unit_testin.GenerateUTPromptReq) ([]map[string]string, error) { 34 | codeLanguageAnnotationFlag := xutils.CodeLanguageAnnotationFlagGet(in.CodeLanguage) 35 | 36 | tmpls := []map[string]string{ 37 | { 38 | "role": "system", 39 | "content": fmt.Sprintf(SystemMessage, "```", in.CodeLanguage), 40 | }, 41 | } 42 | 43 | // 加入用户上下文信息 44 | for _, cContext := range in.SharedContexts { 45 | ctxContext := cContext.Content 46 | var content string 47 | switch cContext.Type { 48 | case consts.ContextFileLocal.String(): 49 | content = fmt.Sprintf("\n\n这是测试代码所在的本地上下文文件:\n\n\n```%s\n%s FILEPATH: %s\n\n%s\n```\n\n因为这是一个本地上下文文件,通常情况下,除非是为了验证预期功能或覆盖边缘情况,否则没有必要引用这个文件。", 50 | in.CodeLanguage, codeLanguageAnnotationFlag, cContext.Path, ctxContext) 51 | case consts.ContextFileLocalTest.String(): 52 | content = fmt.Sprintf("\n\n以下是现有测试文件的摘录:\n\n\n```%s\n%s FILEPATH: %s\n\n%s\n```\n\n因为存在一个现有测试文件:\n- 不要生成前言部分,比如 import、copyright 等。\n- 要生成可以追加到现有测试文件中的代码。", 53 | in.CodeLanguage, codeLanguageAnnotationFlag, cContext.Path, ctxContext) 54 | case consts.ContextFileOtherTest.String(): 55 | content = fmt.Sprintf("这是一个示例测试文件:\n\n\n```%s\n%s FILEPATH: %s\n\n%s\n```", 56 | in.CodeLanguage, codeLanguageAnnotationFlag, cContext.Path, ctxContext) 57 | default: 58 | content = fmt.Sprintf("这是一个示例测试文件:\n\n\n```%s\n%s FILEPATH: %s\n\n%s\n```", 59 | in.CodeLanguage, codeLanguageAnnotationFlag, cContext.Path, ctxContext) 60 | } 61 | 62 | tmpls = append(tmpls, map[string]string{ 63 | "role": "user", 64 | "content": content, 65 | }) 66 | } 67 | 68 | // 加入用户选择的代码 69 | selectedTmpl := []map[string]string{ 70 | { 71 | "role": "user", 72 | "content": fmt.Sprintf("这是用户从文件: `%[1]s`,挑选的 %[2]s 代码,代码内容为:\n```%[2]s\n%[3]s\n```", 73 | in.CodePath, in.CodeLanguage, in.SelectedCode), 74 | }, 75 | } 76 | 77 | // 加入用户的指令 78 | UserUTInstruction := fmt.Sprintf(UserInstruction, in.CodeLanguage, codeLanguageAnnotationFlag, in.Framework, in.UserInstruction) 79 | endTmpl := []map[string]string{ 80 | { 81 | "role": "user", 82 | "content": UserUTInstruction, 83 | }, 84 | } 85 | 86 | tmpls = append(tmpls, selectedTmpl...) 87 | tmpls = append(tmpls, endTmpl...) 88 | 89 | return tmpls, nil 90 | } 91 | -------------------------------------------------------------------------------- /internal/logic/common/code_generate_common.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "context" 5 | "github.com/gogf/gf/v2/frame/g" 6 | "github.com/gogf/gf/v2/os/gtime" 7 | "github.com/gogf/gf/v2/util/gconv" 8 | "xcoder/core/llms" 9 | "xcoder/internal/dao" 10 | "xcoder/internal/dao/mongodb" 11 | "xcoder/internal/model/entity" 12 | "xcoder/internal/model/input/codein" 13 | "xcoder/utility/xconcurrent" 14 | "xcoder/utility/xcontext" 15 | ) 16 | 17 | func getModelNameAndVersionToDB(ctx context.Context, llmModelDefaultParams *codein.CodeGenerateLLMParams) (string, string) { 18 | return llmModelDefaultParams.ModelName, llmModelDefaultParams.ModelVersion 19 | } 20 | 21 | func CodeGenerateRecordInsertDB(ctx context.Context, in *codein.CodeGenerateRecordInsertDBRequest) error { 22 | modelNameToDB, modelVersionToDB := getModelNameAndVersionToDB(ctx, in.LLMParams) 23 | dataToMysql := &entity.PCodeGenRecords{ 24 | Isactive: 1, 25 | DeleteToken: "NA", 26 | Inserttime: gtime.Now(), 27 | Updatetime: gtime.Now(), 28 | GenerateUuid: in.Input.GenerateUUID, 29 | GenerateType: in.GenerateType, 30 | IsSingleLine: gconv.Int(in.IsSingleLine), 31 | CreateUser: in.Input.CreateUser, 32 | GitRepo: in.Input.GitRepo, 33 | GitBranch: in.Input.GitBranch, 34 | CodePath: in.Input.CodePath, 35 | CodeLanguage: in.Input.CodeLanguage, 36 | CodeTotalLines: in.Input.CodeTotalLines, 37 | CrossfileCtxNums: in.CfcNums, 38 | IdeInfo: in.Input.IDEInfo, 39 | StartCursorIdx: gconv.Uint(in.Input.StartCursorIdx), 40 | PrefixCodeTokens: gconv.Uint(llms.CountTokens(modelNameToDB, in.CodeBeforeCursor)), 41 | SuffixCodeTokens: gconv.Uint(llms.CountTokens(modelNameToDB, in.CodeAfterCursor)), 42 | ModelName: modelNameToDB, 43 | ModelVersion: modelVersionToDB, 44 | } 45 | 46 | dataToMongoDB := &codein.CodeGenerateInsertMongoRequest{ 47 | GenerateUUID: in.Input.GenerateUUID, 48 | CodeBeforeCursor: in.CodeBeforeCursor, 49 | CodeAfterCursor: in.CodeAfterCursor, 50 | CodeBeforeWithContext: in.CodeBeforeWithContext, 51 | } 52 | 53 | p := xconcurrent.NewBase("codeGenerateRecordInsertDB") 54 | p.Compute(xcontext.WithProtect(ctx), func(ctx context.Context) error { 55 | g.Log().Infof(ctx, "AsyncCodeGenerateInsertDB start to insert to mysql") 56 | // 请求字段保存到 mysql 57 | err := dao.PCodeGenRecords.Create(ctx, dataToMysql) 58 | if err != nil { 59 | g.Log().Errorf(ctx, "Mysql CodeGenerateInsert failed: %v", err) 60 | return err 61 | } 62 | 63 | // 检索记录保存到 mysql 64 | if len(in.CodeGenSnippetModels) > 0 { 65 | err := dao.PCodeGenRetrievalSnippetMap.BatchCreate(ctx, in.CodeGenSnippetModels) 66 | if err != nil { 67 | g.Log().Errorf(ctx, "Mysql CodeGenerateRetrievalSnippetMapBatchCreate failed: %v", err) 68 | return err 69 | } 70 | g.Log().Infof(ctx, "Mysql CodeGenerateRetrievalSnippetMapBatchCreate success, "+ 71 | "generateUUID: %s", in.Input.GenerateUUID) 72 | } 73 | 74 | g.Log().Infof(ctx, "AsyncCodeGenerateInsertDB start to insert to mongodb") 75 | // 保存代码到 mongodb 76 | _, err = mongodb.MDao.CodeGenerateInsert(ctx, dataToMongoDB) 77 | if err != nil { 78 | g.Log().Errorf(ctx, "Mongodb CodeGenerateInsert failed: %v", err) 79 | return err 80 | } 81 | 82 | g.Log().Infof(ctx, "AsyncCodeGenerateInsertDB finished") 83 | return nil 84 | }) 85 | 86 | return nil 87 | } 88 | -------------------------------------------------------------------------------- /core/llms/count_token.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Travis Cline 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package llms 22 | 23 | import ( 24 | "context" 25 | "github.com/gogf/gf/v2/frame/g" 26 | "github.com/pkoukk/tiktoken-go" 27 | ) 28 | 29 | const ( 30 | _tokenApproximation = 4 31 | ) 32 | 33 | const ( 34 | _gpt35TurboContextSize = 4096 35 | _gpt432KContextSize = 32768 36 | _gpt4ContextSize = 8192 37 | _textDavinci3ContextSize = 4097 38 | _textBabbage1ContextSize = 2048 39 | _textAda1ContextSize = 2048 40 | _textCurie1ContextSize = 2048 41 | _codeDavinci2ContextSize = 8000 42 | _codeCushman1ContextSize = 2048 43 | _textBisonContextSize = 2048 44 | _chatBisonContextSize = 2048 45 | _defaultContextSize = 2048 46 | ) 47 | 48 | // nolint:gochecknoglobals 49 | var modelToContextSize = map[string]int{ 50 | "gpt-3.5-turbo": _gpt35TurboContextSize, 51 | "gpt-4-32k": _gpt432KContextSize, 52 | "gpt-4": _gpt4ContextSize, 53 | "text-davinci-003": _textDavinci3ContextSize, 54 | "text-curie-001": _textCurie1ContextSize, 55 | "text-babbage-001": _textBabbage1ContextSize, 56 | "text-ada-001": _textBabbage1ContextSize, 57 | "code-davinci-002": _codeDavinci2ContextSize, 58 | "code-cushman-001": _codeCushman1ContextSize, 59 | } 60 | 61 | // GetModelContextSize gets the max number of tokens for a language model. If the model 62 | // name isn't recognized the default value 2048 is returned. 63 | func GetModelContextSize(model string) int { 64 | contextSize, ok := modelToContextSize[model] 65 | if !ok { 66 | return _defaultContextSize 67 | } 68 | return contextSize 69 | } 70 | 71 | // CountTokens gets the number of tokens the text contains. 72 | func CountTokens(model, text string) int { 73 | e, err := tiktoken.EncodingForModel(model) 74 | if err != nil { 75 | e, err = tiktoken.GetEncoding("gpt2") 76 | if err != nil { 77 | g.Log().Infof(context.Background(), "[INFO] Can not use model to calculate number of tokens, "+ 78 | "falling back to approximate count") 79 | return len([]rune(text)) / _tokenApproximation 80 | } 81 | } 82 | return len(e.Encode(text, nil, nil)) 83 | } 84 | 85 | // CalculateMaxTokens calculates the max number of tokens that could be added to a text. 86 | func CalculateMaxTokens(model, text string) int { 87 | return GetModelContextSize(model) - CountTokens(model, text) 88 | } 89 | -------------------------------------------------------------------------------- /internal/controller/utils/response/response.go: -------------------------------------------------------------------------------- 1 | package response 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "github.com/gogf/gf/v2/frame/g" 8 | "github.com/gogf/gf/v2/net/ghttp" 9 | "github.com/gogf/gf/v2/os/gctx" 10 | "net/http" 11 | "xcoder/api/common" 12 | "xcoder/internal/controller/utils/ccode" 13 | "xcoder/internal/controller/utils/cerror" 14 | ) 15 | 16 | func JsonSuccess(r *ghttp.Request, data interface{}) { 17 | r.Response.WriteHeader(http.StatusOK) 18 | r.Response.WriteJson(&common.Response{ 19 | Code: ccode.CodeSuccess, 20 | Message: ccode.CodeSuccessMessage, 21 | Data: data, 22 | }) 23 | } 24 | 25 | func JsonUserError(r *ghttp.Request, err *cerror.UserError) { 26 | r.Response.WriteHeader(http.StatusBadRequest) 27 | r.Response.WriteJsonExit(&common.Response{ 28 | Code: err.Code, 29 | Message: fmt.Sprintf("err msg: %s, traceID: %v", err.Error(), gctx.CtxId(r.Context())), 30 | Data: nil, 31 | }) 32 | } 33 | 34 | func JsonUserErrorButHttpOk(r *ghttp.Request, err *cerror.UserErrorButHttpOk) { 35 | r.Response.WriteHeader(http.StatusOK) 36 | r.Response.WriteJsonExit(&common.Response{ 37 | Code: err.Code, 38 | Message: fmt.Sprintf("err msg: %s, traceID: %v", err.Error(), gctx.CtxId(r.Context())), 39 | Data: nil, 40 | }) 41 | } 42 | 43 | func JsonInternalServerError(r *ghttp.Request, err *cerror.ServerError) { 44 | r.Response.WriteHeader(http.StatusInternalServerError) 45 | r.Response.WriteJsonExit(&common.Response{ 46 | Code: err.Code, 47 | Message: fmt.Sprintf("err msg: %s, traceID: %v", "internal server error", gctx.CtxId(r.Context())), 48 | Data: nil, 49 | }) 50 | } 51 | 52 | func JsonUnauthorizedError(r *ghttp.Request, err *cerror.UnauthorizedError) { 53 | r.Response.WriteHeader(http.StatusUnauthorized) 54 | r.Response.WriteJsonExit(&common.Response{ 55 | Code: err.Code, 56 | Message: fmt.Sprintf("err msg: %s, traceId: %v", err.Error(), gctx.CtxId(r.Context())), 57 | Data: nil, 58 | }) 59 | } 60 | 61 | func SseSendSuccess(ctx context.Context, r *ghttp.Request, data string) error { 62 | resp := &common.Response{ 63 | Code: 0, 64 | Message: "", 65 | Data: data, 66 | } 67 | respJson, err := json.Marshal(resp) 68 | if err != nil { 69 | g.Log().Errorf(ctx, "SseSendSuccess json.Marshal err: %v", err) 70 | return err 71 | } 72 | message := fmt.Sprintf("data: %s\n\n", respJson) 73 | 74 | r.Response.WriteHeader(http.StatusOK) 75 | r.Response.Write([]byte(message)) 76 | 77 | return nil 78 | } 79 | 80 | func SseSendInternalServerError(ctx context.Context, r *ghttp.Request) error { 81 | resp := &common.Response{ 82 | Code: 500000, 83 | Message: "internal server error", 84 | Data: "", 85 | } 86 | respJson, err := json.Marshal(resp) 87 | if err != nil { 88 | g.Log().Errorf(ctx, "SseSendInternalServerError json.Marshal err: %v", err) 89 | return err 90 | } 91 | 92 | message := fmt.Sprintf("data: %s\n\n", respJson) 93 | r.Response.WriteHeader(http.StatusInternalServerError) 94 | r.Response.WriteExit([]byte(message)) 95 | 96 | return nil 97 | } 98 | 99 | func SseSendUserError(ctx context.Context, r *ghttp.Request, code int, errMsg string) error { 100 | resp := &common.Response{ 101 | Code: code, 102 | Message: errMsg, 103 | Data: "", 104 | } 105 | respJson, err := json.Marshal(resp) 106 | if err != nil { 107 | g.Log().Errorf(ctx, "SseSendUserError json.Marshal err: %v", err) 108 | return err 109 | } 110 | message := fmt.Sprintf("data: %s\n\n", respJson) 111 | 112 | r.Response.WriteHeader(http.StatusBadRequest) 113 | r.Response.WriteExit([]byte(message)) 114 | 115 | return nil 116 | } 117 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module xcoder 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/fsnotify/fsnotify v1.7.0 7 | github.com/gogf/gf/contrib/drivers/mysql/v2 v2.7.2 8 | github.com/gogf/gf/v2 v2.7.2 9 | github.com/google/go-cmp v0.6.0 10 | github.com/pkoukk/tiktoken-go v0.1.7 11 | github.com/sergi/go-diff v1.0.0 12 | github.com/spf13/viper v1.19.0 13 | github.com/stretchr/testify v1.9.0 14 | github.com/tmc/langchaingo v0.0.0-20230929160525-e16b77704b8d 15 | go.mongodb.org/mongo-driver v1.11.3 16 | google.golang.org/protobuf v1.33.0 17 | gopkg.in/guregu/null.v3 v3.5.0 18 | ) 19 | 20 | require ( 21 | github.com/BurntSushi/toml v1.3.2 // indirect 22 | github.com/Masterminds/goutils v1.1.1 // indirect 23 | github.com/Masterminds/semver/v3 v3.2.0 // indirect 24 | github.com/Masterminds/sprig/v3 v3.2.3 // indirect 25 | github.com/clbanning/mxj/v2 v2.7.0 // indirect 26 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 27 | github.com/dlclark/regexp2 v1.10.0 // indirect 28 | github.com/emirpasic/gods v1.18.1 // indirect 29 | github.com/fatih/color v1.16.0 // indirect 30 | github.com/go-logr/logr v1.4.1 // indirect 31 | github.com/go-logr/stdr v1.2.2 // indirect 32 | github.com/go-sql-driver/mysql v1.7.1 // indirect 33 | github.com/golang/snappy v0.0.4 // indirect 34 | github.com/google/uuid v1.6.0 // indirect 35 | github.com/gorilla/websocket v1.5.1 // indirect 36 | github.com/grokify/html-strip-tags-go v0.1.0 // indirect 37 | github.com/hashicorp/hcl v1.0.0 // indirect 38 | github.com/huandu/xstrings v1.3.3 // indirect 39 | github.com/imdario/mergo v0.3.12 // indirect 40 | github.com/klauspost/compress v1.17.2 // indirect 41 | github.com/magiconair/properties v1.8.7 // indirect 42 | github.com/mattn/go-colorable v0.1.13 // indirect 43 | github.com/mattn/go-isatty v0.0.20 // indirect 44 | github.com/mattn/go-runewidth v0.0.15 // indirect 45 | github.com/mitchellh/copystructure v1.0.0 // indirect 46 | github.com/mitchellh/mapstructure v1.5.0 // indirect 47 | github.com/mitchellh/reflectwalk v1.0.0 // indirect 48 | github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect 49 | github.com/olekukonko/tablewriter v0.0.5 // indirect 50 | github.com/pelletier/go-toml/v2 v2.2.2 // indirect 51 | github.com/pkg/errors v0.9.1 // indirect 52 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 53 | github.com/rivo/uniseg v0.4.4 // indirect 54 | github.com/sagikazarmark/locafero v0.6.0 // indirect 55 | github.com/sagikazarmark/slog-shim v0.1.0 // indirect 56 | github.com/shopspring/decimal v1.2.0 // indirect 57 | github.com/sourcegraph/conc v0.3.0 // indirect 58 | github.com/spf13/afero v1.11.0 // indirect 59 | github.com/spf13/cast v1.6.0 // indirect 60 | github.com/spf13/pflag v1.0.5 // indirect 61 | github.com/stretchr/objx v0.5.2 // indirect 62 | github.com/subosito/gotenv v1.6.0 // indirect 63 | github.com/xdg-go/pbkdf2 v1.0.0 // indirect 64 | github.com/xdg-go/scram v1.1.1 // indirect 65 | github.com/xdg-go/stringprep v1.0.3 // indirect 66 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect 67 | go.opentelemetry.io/otel v1.24.0 // indirect 68 | go.opentelemetry.io/otel/metric v1.24.0 // indirect 69 | go.opentelemetry.io/otel/sdk v1.24.0 // indirect 70 | go.opentelemetry.io/otel/trace v1.24.0 // indirect 71 | go.uber.org/multierr v1.11.0 // indirect 72 | golang.org/x/crypto v0.22.0 // indirect 73 | golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect 74 | golang.org/x/net v0.24.0 // indirect 75 | golang.org/x/sync v0.7.0 // indirect 76 | golang.org/x/sys v0.22.0 // indirect 77 | golang.org/x/text v0.16.0 // indirect 78 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect 79 | gopkg.in/ini.v1 v1.67.0 // indirect 80 | gopkg.in/yaml.v2 v2.4.0 // indirect 81 | gopkg.in/yaml.v3 v3.0.1 // indirect 82 | ) 83 | -------------------------------------------------------------------------------- /internal/controller/utils/app_utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/gogf/gf/v2/frame/g" 7 | "github.com/gogf/gf/v2/net/ghttp" 8 | "strings" 9 | "time" 10 | "xcoder/internal/controller/utils/response" 11 | "xcoder/internal/model/input/chatin" 12 | ) 13 | 14 | type AnnotatingCodeRequest struct { 15 | Language string `json:"language"` 16 | Code string `json:"code"` 17 | } 18 | 19 | type AnnotatingCodeResponse struct { 20 | Code string `json:"code"` 21 | } 22 | 23 | var LanguageAnnotatingCodeFlagMap = map[string]string{ 24 | "python": "#", 25 | "go": "//", 26 | "java": "//", 27 | "javascript": "//", 28 | "typescript": "//", 29 | "kotlin": "//", 30 | "groovy": "//", 31 | "c": "//", 32 | "c++": "//", 33 | "c#": "//", 34 | "ruby": "#", 35 | "perl": "#", 36 | "php": "//", 37 | "swift": "//", 38 | "scala": "//", 39 | "rust": "//", 40 | "objective-c": "//", 41 | "erlang": "%", 42 | "elixir": "%", 43 | "haskell": "--", 44 | "lisp": ";", 45 | } 46 | 47 | func AnnotatingCodeWithLanguage(ctx context.Context, req *AnnotatingCodeRequest) (*AnnotatingCodeResponse, error) { 48 | codeLanguage := strings.ToLower(req.Language) 49 | flag, ok := LanguageAnnotatingCodeFlagMap[codeLanguage] 50 | if !ok { 51 | g.Log().Errorf(ctx, "language not support: %s", req.Language) 52 | return &AnnotatingCodeResponse{Code: req.Code}, fmt.Errorf("language not support: %s", req.Language) 53 | } 54 | 55 | codes := strings.Split(req.Code, "\n") 56 | for i, code := range codes { 57 | if strings.TrimSpace(code) == "" { 58 | continue 59 | } 60 | codes[i] = flag + " " + code 61 | } 62 | req.Code = strings.Join(codes, "\n") 63 | 64 | return &AnnotatingCodeResponse{ 65 | Code: req.Code, 66 | }, nil 67 | } 68 | 69 | func GetStreamingChatReq(ctx context.Context) (r *ghttp.Request) { 70 | r = g.RequestFromCtx(ctx) 71 | 72 | r.Response.Header().Set("Content-Type", "text/event-stream") 73 | r.Response.Header().Set("Cache-Control", "no-cache") 74 | r.Response.Header().Set("Connection", "keep-alive") 75 | r.Response.Header().Set("Access-Control-Allow-Origin", "*") 76 | r.Response.Header().Set("Access-Control-Allow-Methods", "GET, POST") 77 | r.Response.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization") 78 | r.Response.Header().Set("Access-Control-Allow-Credentials", "true") 79 | r.Response.Header().Set("Access-Control-Max-Age", "86400") 80 | 81 | return 82 | } 83 | 84 | func ParseStreamingChatResp(ctx context.Context, r *ghttp.Request, result chan *chatin.ChatResult) { 85 | for { 86 | select { 87 | case res := <-result: 88 | if res.Error != nil { 89 | g.Log().Errorf(ctx, "ParseStreamingChatResp error: %v", res.Error) 90 | err := response.SseSendInternalServerError(ctx, r) 91 | if err != nil { 92 | g.Log().Errorf(ctx, "Chat sse response err: %v", err) 93 | return 94 | } 95 | } else { 96 | data := res.Data 97 | err := response.SseSendSuccess(ctx, r, data) 98 | if err != nil { 99 | g.Log().Errorf(ctx, "Chat sse response err: %v", err) 100 | return 101 | } 102 | 103 | r.Response.Flush() 104 | 105 | if data == "[DONE]" { 106 | g.Log().Infof(ctx, "Chat sse response done ...") 107 | r.Response.Request.Exit() 108 | return 109 | } 110 | } 111 | 112 | case <-ctx.Done(): 113 | g.Log().Infof(ctx, "Chat sse response done ...") 114 | r.Response.Request.Exit() 115 | return 116 | 117 | case <-time.After(15 * time.Second): 118 | g.Log().Errorf(ctx, "Chat sse response timeout ...") 119 | err := response.SseSendUserError(ctx, r, 400003, "chat sse response timeout") 120 | if err != nil { 121 | g.Log().Errorf(ctx, "Chat sse response response err: %v", err) 122 | return 123 | } 124 | return 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /core/llms/vllm/xcoder/xcoderllm.go: -------------------------------------------------------------------------------- 1 | package xcoder 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "github.com/gogf/gf/v2/frame/g" 7 | "os" 8 | "time" 9 | "xcoder/core/callbacks" 10 | "xcoder/core/llms" 11 | "xcoder/core/llms/vllm/xcoder/internal/xcoderclient" 12 | "xcoder/utility/xcontext" 13 | "xcoder/utility/xutils" 14 | ) 15 | 16 | type LLM struct { 17 | CallBacksHandler callbacks.Handler 18 | client *xcoderclient.Client 19 | } 20 | 21 | func New(opts ...Option) (*LLM, error) { 22 | c, err := newClient(opts...) 23 | return &LLM{ 24 | CallBacksHandler: callbacks.HandlerImpl{}, 25 | client: c, 26 | }, err 27 | } 28 | 29 | var ( 30 | ErrEmptyResponse = errors.New("empty response") 31 | ) 32 | 33 | func newClient(opts ...Option) (*xcoderclient.Client, error) { 34 | options := &options{ 35 | model: os.Getenv(modelEnvVarName), 36 | baseURL: os.Getenv(baseURLEnvVarName), 37 | } 38 | for _, opt := range opts { 39 | opt(options) 40 | } 41 | 42 | return xcoderclient.New(options.model, options.baseURL), nil 43 | } 44 | 45 | func (c *LLM) Predict(ctx context.Context, prompt string, options ...llms.CallOption) (string, error) { 46 | r, err := c.Generate(ctx, []string{prompt}, options...) 47 | if err != nil { 48 | return "", err 49 | } 50 | return r[0].Text, nil 51 | } 52 | 53 | func (c *LLM) Generate(ctx context.Context, prompts []string, options ...llms.CallOption) ([]*llms.Generation, error) { 54 | opts := llms.CallOptions{} 55 | for _, opt := range options { 56 | opt(&opts) 57 | } 58 | 59 | generations := make([]*llms.Generation, 0, len(prompts)) 60 | for _, p := range prompts { 61 | startTime := time.Now() 62 | result, err := c.client.CreateCodeGenWithDetail(ctx, &xcoderclient.CodeGenRequest{ 63 | Model: opts.Model, 64 | Prompt: p, 65 | MaxTokens: opts.MaxTokens, 66 | StopWords: opts.StopWords, 67 | Temperature: opts.Temperature, 68 | FrequencyPenalty: opts.FrequencyPenalty, 69 | PresencePenalty: opts.PresencePenalty, 70 | TopP: opts.TopP, 71 | StreamingFunc: opts.StreamingFunc, 72 | }) 73 | if err != nil { 74 | return nil, err 75 | } 76 | if len(result.Choices) == 0 { 77 | return nil, ErrEmptyResponse 78 | } 79 | 80 | elapsedTime := time.Since(startTime) 81 | generationInfo := make(map[string]any) 82 | generationInfo["completion_code_tokens"] = result.Usage.CompletionTokens 83 | generationInfo["prompt_tokens"] = result.Usage.PromptTokens 84 | generationInfo["total_tokens"] = result.Usage.TotalTokens 85 | generationInfo["finish_reason"] = result.Choices[0].FinishReason 86 | generationInfo["failure_reason"] = "" 87 | generationInfo["completion_duration"] = int(elapsedTime.Milliseconds()) 88 | generationInfo["generate_uuid"] = opts.UUID 89 | generationInfo["completion_code"] = result.Choices[0].Text 90 | 91 | generateText := result.Choices[0].Text 92 | // 如果停止原因是 length,则将最后一行的代码去除 93 | if generationInfo["finish_reason"] == "length" { 94 | generateText = xutils.HandleGenerateTextWithLengthStop(ctx, generateText) 95 | } 96 | // 如果是多行生成,去除重复的代码 97 | if opts.IsMultiLine { 98 | generateText = xutils.HandleRemoveDuplicateCodeForMultiLine(ctx, generateText, opts.SuffixCode) 99 | } 100 | // 如果是单行生成,去除尾部的空格 101 | if !opts.IsMultiLine { 102 | generateText = xutils.HandleRemoveTailSpaceIfExistForSingleLine(ctx, generateText) 103 | } 104 | 105 | generations = append(generations, &llms.Generation{ 106 | Text: generateText, 107 | GenerationInfo: generationInfo, 108 | }) 109 | } 110 | 111 | g.Log().Infof(ctx, "HandleLLMPredictEnd start") 112 | err := c.CallBacksHandler.HandleLLMPredictEnd(xcontext.WithProtect(ctx), llms.LLMResult{ 113 | Generations: generations, 114 | }) 115 | if err != nil { 116 | g.Log().Errorf(ctx, "HandleLLMPredictEnd error: %v", err) 117 | return nil, err 118 | } 119 | 120 | return generations, nil 121 | } 122 | -------------------------------------------------------------------------------- /internal/dao/internal/p_code_gen_retrieval_snippet_map.go: -------------------------------------------------------------------------------- 1 | // ========================================================================== 2 | // Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. 3 | // ========================================================================== 4 | 5 | package internal 6 | 7 | import ( 8 | "context" 9 | 10 | "github.com/gogf/gf/v2/database/gdb" 11 | "github.com/gogf/gf/v2/frame/g" 12 | ) 13 | 14 | // PCodeGenRetrievalSnippetMapDao is the data access object for table p_code_gen_retrieval_snippet_map. 15 | type PCodeGenRetrievalSnippetMapDao struct { 16 | table string // table is the underlying table name of the DAO. 17 | group string // group is the database configuration group name of current DAO. 18 | columns PCodeGenRetrievalSnippetMapColumns // columns contains all the column names of Table for convenient usage. 19 | } 20 | 21 | // PCodeGenRetrievalSnippetMapColumns defines and stores column names for table p_code_gen_retrieval_snippet_map. 22 | type PCodeGenRetrievalSnippetMapColumns struct { 23 | Updatetime string // 24 | Inserttime string // 25 | Isactive string // 26 | DeleteToken string // 27 | Id string // 28 | GenerateUuid string // 29 | GitRepo string // 30 | GitBranch string // 31 | SnippetUuid string // 32 | SnippetScore string // 33 | SnippetRepo string // 34 | SnippetPath string // 35 | SnippetContent string // 36 | ProjectName string // 37 | ProjectVersion string // 38 | CreateUser string // 39 | } 40 | 41 | // pCodeGenRetrievalSnippetMapColumns holds the columns for table p_code_gen_retrieval_snippet_map. 42 | var pCodeGenRetrievalSnippetMapColumns = PCodeGenRetrievalSnippetMapColumns{ 43 | Updatetime: "updatetime", 44 | Inserttime: "inserttime", 45 | Isactive: "isactive", 46 | DeleteToken: "delete_token", 47 | Id: "id", 48 | GenerateUuid: "generate_uuid", 49 | GitRepo: "git_repo", 50 | GitBranch: "git_branch", 51 | SnippetUuid: "snippet_uuid", 52 | SnippetScore: "snippet_score", 53 | SnippetRepo: "snippet_repo", 54 | SnippetPath: "snippet_path", 55 | SnippetContent: "snippet_content", 56 | ProjectName: "project_name", 57 | ProjectVersion: "project_version", 58 | CreateUser: "create_user", 59 | } 60 | 61 | // NewPCodeGenRetrievalSnippetMapDao creates and returns a new DAO object for table data access. 62 | func NewPCodeGenRetrievalSnippetMapDao() *PCodeGenRetrievalSnippetMapDao { 63 | return &PCodeGenRetrievalSnippetMapDao{ 64 | group: "default", 65 | table: "p_code_gen_retrieval_snippet_map", 66 | columns: pCodeGenRetrievalSnippetMapColumns, 67 | } 68 | } 69 | 70 | // DB retrieves and returns the underlying raw database management object of current DAO. 71 | func (dao *PCodeGenRetrievalSnippetMapDao) DB() gdb.DB { 72 | return g.DB(dao.group) 73 | } 74 | 75 | // Table returns the table name of current dao. 76 | func (dao *PCodeGenRetrievalSnippetMapDao) Table() string { 77 | return dao.table 78 | } 79 | 80 | // Columns returns all column names of current dao. 81 | func (dao *PCodeGenRetrievalSnippetMapDao) Columns() PCodeGenRetrievalSnippetMapColumns { 82 | return dao.columns 83 | } 84 | 85 | // Group returns the configuration group name of database of current dao. 86 | func (dao *PCodeGenRetrievalSnippetMapDao) Group() string { 87 | return dao.group 88 | } 89 | 90 | // Ctx creates and returns the Model for current DAO, It automatically sets the context for current operation. 91 | func (dao *PCodeGenRetrievalSnippetMapDao) Ctx(ctx context.Context) *gdb.Model { 92 | return dao.DB().Model(dao.table).Safe().Ctx(ctx) 93 | } 94 | 95 | // Transaction wraps the transaction logic using function f. 96 | // It rollbacks the transaction and returns the error from function f if it returns non-nil error. 97 | // It commits the transaction and returns nil if function f returns nil. 98 | // 99 | // Note that, you should not Commit or Rollback the transaction in function f 100 | // as it is automatically handled by this function. 101 | func (dao *PCodeGenRetrievalSnippetMapDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) { 102 | return dao.Ctx(ctx).Transaction(ctx, f) 103 | } 104 | -------------------------------------------------------------------------------- /core/llms/vllm/xcoder/internal/xcoderclient/code.go: -------------------------------------------------------------------------------- 1 | package xcoderclient 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/json" 7 | "errors" 8 | "fmt" 9 | "github.com/gogf/gf/v2/frame/g" 10 | "github.com/gogf/gf/v2/util/gconv" 11 | "io" 12 | "net/http" 13 | ) 14 | 15 | type CodeGenRequest struct { 16 | Model string `json:"model"` 17 | Prompt string `json:"prompt"` 18 | Temperature float64 `json:"temperature"` 19 | MaxTokens int `json:"max_tokens"` 20 | FrequencyPenalty float64 `json:"frequency_penalty,omitempty"` 21 | PresencePenalty float64 `json:"presence_penalty,omitempty"` 22 | TopP float64 `json:"top_p,omitempty"` 23 | StopWords []string `json:"stop,omitempty"` 24 | Stream bool `json:"stream,omitempty"` 25 | 26 | // StreamingFunc is a function to be called for each chunk of a streaming response. 27 | // Return an error to stop streaming early. 28 | StreamingFunc func(ctx context.Context, chunk []byte) error `json:"-"` 29 | } 30 | 31 | type CodeGenModelResponse struct { 32 | ID string `json:"id,omitempty"` 33 | Created float64 `json:"created,omitempty"` 34 | Choices []struct { 35 | FinishReason string `json:"finish_reason,omitempty"` 36 | Index float64 `json:"index,omitempty"` 37 | Logprobs interface{} `json:"logprobs,omitempty"` 38 | Text string `json:"text,omitempty"` 39 | } `json:"choices,omitempty"` 40 | Model string `json:"model,omitempty"` 41 | Object string `json:"object,omitempty"` 42 | Usage struct { 43 | CompletionTokens int `json:"completion_tokens,omitempty"` 44 | PromptTokens int `json:"prompt_tokens,omitempty"` 45 | TotalTokens int `json:"total_tokens,omitempty"` 46 | } `json:"usage,omitempty"` 47 | } 48 | 49 | func (c *Client) setCodeGenDefaults(ctx context.Context, payload *CodeGenRequest) { 50 | if payload.MaxTokens == 0 { 51 | payload.MaxTokens = 160 52 | } 53 | 54 | if len(payload.StopWords) == 0 { 55 | payload.StopWords = nil 56 | } 57 | } 58 | 59 | func (c *Client) createCodeGen(ctx context.Context, payload *CodeGenRequest) (*CodeGenModelResponse, error) { 60 | g.Log().Infof(ctx, "=== Model: %s, baseUrl: %s ===", c.Model, c.baseURL) 61 | c.setCodeGenDefaults(ctx, payload) 62 | return c.createCodeGenDo(ctx, &CodeGenRequest{ 63 | Model: payload.Model, 64 | Prompt: payload.Prompt, 65 | Temperature: payload.Temperature, 66 | TopP: payload.TopP, 67 | MaxTokens: payload.MaxTokens, 68 | StopWords: payload.StopWords, 69 | FrequencyPenalty: payload.FrequencyPenalty, 70 | PresencePenalty: payload.PresencePenalty, 71 | StreamingFunc: payload.StreamingFunc, 72 | }) 73 | } 74 | 75 | func (c *Client) createCodeGenDo(ctx context.Context, payload *CodeGenRequest) (*CodeGenModelResponse, error) { 76 | if payload.StreamingFunc != nil { 77 | payload.Stream = true 78 | } 79 | 80 | payloadBytes, err := json.Marshal(payload) 81 | if err != nil { 82 | return nil, err 83 | } 84 | g.Log().Infof(ctx, "=== payloadBytes: %s === ", string(payloadBytes)) 85 | 86 | body := bytes.NewReader(payloadBytes) 87 | req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.baseURL+"/v1/completions", body) 88 | if err != nil { 89 | return nil, err 90 | } 91 | 92 | r, err := http.DefaultClient.Do(req) 93 | if err != nil { 94 | return nil, err 95 | } 96 | defer r.Body.Close() 97 | 98 | if r.StatusCode != http.StatusOK { 99 | msg := fmt.Sprintf("API returned unexpected status code: %d", r.StatusCode) 100 | 101 | var errResp errorMessage 102 | if err := json.NewDecoder(r.Body).Decode(&errResp); err != nil { 103 | return nil, errors.New(msg) 104 | } 105 | 106 | return nil, fmt.Errorf("%s: %s", msg, errResp.Error.Message) 107 | } 108 | 109 | content, err := io.ReadAll(r.Body) 110 | 111 | if payload.StreamingFunc != nil { 112 | parseStreamingCodeGenResponse(ctx, r, payload) 113 | } 114 | 115 | var response CodeGenModelResponse 116 | err = gconv.Struct(content, &response) 117 | if err != nil { 118 | return nil, err 119 | } 120 | return &response, nil 121 | } 122 | 123 | func parseStreamingCodeGenResponse(ctx context.Context, r *http.Response, payload *CodeGenRequest) { 124 | fmt.Println("Implement me") 125 | } 126 | -------------------------------------------------------------------------------- /internal/model/input/chatin/chat.go: -------------------------------------------------------------------------------- 1 | package chatin 2 | 3 | import ( 4 | "xcoder/core/schema" 5 | ) 6 | 7 | type Context struct { 8 | Type string `json:"type" binding:"omitempty"` 9 | Path string `json:"path" binding:"omitempty"` 10 | Content string `json:"content" binding:"omitempty"` 11 | } 12 | 13 | type ChatSseGenerateReq struct { 14 | CreateUser string `json:"createUser" binding:"omitempty"` 15 | ConversationUUID string `json:"conversationUUID" binding:"required"` 16 | // git 信息 17 | GitRepo string `json:"gitRepo" binding:"omitempty"` 18 | GitBranch string `json:"gitBranch" binding:"omitempty"` 19 | // code 信息 20 | CodePath string `json:"codePath" binding:"omitempty"` 21 | CodeLanguage string `json:"codeLanguage" binding:"omitempty"` 22 | // IDE 信息 23 | IdeInfo string `json:"ideInfo" binding:"required"` 24 | ProjectVersion string `json:"projectVersion" binding:"required"` 25 | // 聊天信息 26 | Message []schema.ChatMessageRequest `json:"message"` 27 | // 上下文信息 28 | UserCode string `json:"userCode" binding:"omitempty"` 29 | Context []Context `json:"context"` 30 | } 31 | 32 | type ChatSseGenerateRes struct{} 33 | 34 | type ChatResult struct { 35 | Error error `json:"error"` 36 | Data string `json:"data"` 37 | } 38 | 39 | type ChatLLMRunReq struct { 40 | Input *ChatSseGenerateReq 41 | Output chan *ChatResult 42 | ConversationType string 43 | Prompt []map[string]string 44 | PromptVersion string 45 | } 46 | 47 | type ChatMessageInsertDBReq struct { 48 | Input *ChatSseGenerateReq 49 | LLMConfig *ChatLLMParams 50 | Out chan *ChatResult 51 | ConversationType string 52 | Prompt string 53 | PromptVersion string 54 | } 55 | 56 | type CallLLMReq struct { 57 | Input *ChatSseGenerateReq 58 | LLMConfig *ChatLLMParams 59 | Prompt []map[string]string 60 | Out chan *ChatResult 61 | } 62 | 63 | type ChatMessageUpdateMysqlReq struct { 64 | ConversationUUID string `json:"conversationUUID" binding:"required"` 65 | PromptTokens int `json:"prompt_tokens" binding:"required"` 66 | CompletionTokens int `json:"completion_tokens" binding:"required"` 67 | TotalTokens int `json:"total_tokens" binding:"required"` 68 | CompletionDuration int `json:"completion_duration" binding:"required"` 69 | } 70 | 71 | type ChatMessageUpdateMysqlRes struct{} 72 | 73 | type ChatLLMParamsConfig struct { 74 | Config *ChatLLMParams `json:"config"` 75 | } 76 | 77 | type ChatLLMParams struct { 78 | LLMType string `json:"llmType"` 79 | LLMParams *LLMParams `json:"llmParams"` 80 | } 81 | 82 | type LLMParams struct { 83 | APIBase string `json:"apiBase"` 84 | APIKey string `json:"apiKey"` 85 | APIVersion string `json:"apiVersion"` 86 | Model string `json:"model"` 87 | Temperature float64 `json:"temperature"` 88 | MaxTokens int `json:"maxTokens"` 89 | TopP float64 `json:"topP"` 90 | PresencePenalty float64 `json:"presencePenalty"` 91 | FrequencyPenalty float64 `json:"frequencyPenalty"` 92 | } 93 | 94 | type GenerateChatPromptReq struct { 95 | RepoName string `json:"repoName" binding:"required"` 96 | CodePath string `json:"codePath" binding:"required"` 97 | CodeLanguage string `json:"codeLanguage" binding:"required"` 98 | Contexts []Context `json:"Contexts" binding:"omitempty"` 99 | SelectedCode string `json:"selectedCode" binding:"required"` 100 | Messages []schema.ChatMessageRequest `json:"messages"` 101 | } 102 | 103 | type ChatMessageInsertMongoRequest struct { 104 | ConversationUUID string `json:"conversationUUID" binding:"required"` 105 | CreateUser string `json:"createUser" binding:"required"` 106 | SelectedCode string `json:"selectedCode" binding:"required"` 107 | Messages []schema.ChatMessageRequest `json:"messages"` 108 | } 109 | 110 | type ChatMessageUpdateMongoRequest struct { 111 | ConversationUUID string `json:"conversationUUID" binding:"required"` 112 | Response string `json:"response" binding:"required"` 113 | CompletionCode string `json:"completion_code" binding:"required"` 114 | } 115 | -------------------------------------------------------------------------------- /core/llms/openai/openaillm_option.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Travis Cline 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package openai 22 | 23 | import ( 24 | "xcoder/core/callbacks" 25 | "xcoder/core/llms/openai/internal/openaiclient" 26 | ) 27 | 28 | const ( 29 | tokenEnvVarName = "OPENAI_API_KEY" //nolint:gosec 30 | modelEnvVarName = "OPENAI_MODEL" //nolint:gosec 31 | baseURLEnvVarName = "OPENAI_BASE_URL" //nolint:gosec 32 | baseAPIBaseEnvVarName = "OPENAI_API_BASE" //nolint:gosec 33 | organizationEnvVarName = "OPENAI_ORGANIZATION" //nolint:gosecint:gosec 34 | ) 35 | 36 | type APIType openaiclient.APIType 37 | 38 | const ( 39 | APITypeOpenAI APIType = APIType(openaiclient.APITypeOpenAI) 40 | APITypeAzure = APIType(openaiclient.APITypeAzure) 41 | APITypeAzureAD = APIType(openaiclient.APITypeAzureAD) 42 | ) 43 | 44 | const ( 45 | DefaultAPIVersion = "2023-05-15" 46 | ) 47 | 48 | type options struct { 49 | token string 50 | model string 51 | baseURL string 52 | organization string 53 | apiType APIType 54 | httpClient openaiclient.Doer 55 | 56 | // required when APIType is APITypeAzure or APITypeAzureAD 57 | apiVersion string 58 | embeddingModel string 59 | 60 | callbackHandler callbacks.Handler 61 | } 62 | 63 | type Option func(*options) 64 | 65 | // WithToken passes the OpenAI API token to the client. If not set, the token 66 | // is read from the OPENAI_API_KEY environment variable. 67 | func WithToken(token string) Option { 68 | return func(opts *options) { 69 | opts.token = token 70 | } 71 | } 72 | 73 | // WithModel passes the OpenAI model to the client. If not set, the model 74 | // is read from the OPENAI_MODEL environment variable. 75 | // Required when ApiType is Azure. 76 | func WithModel(model string) Option { 77 | return func(opts *options) { 78 | opts.model = model 79 | } 80 | } 81 | 82 | // WithEmbeddingModel passes the OpenAI model to the client. Required when ApiType is Azure. 83 | func WithEmbeddingModel(embeddingModel string) Option { 84 | return func(opts *options) { 85 | opts.embeddingModel = embeddingModel 86 | } 87 | } 88 | 89 | // WithBaseURL passes the OpenAI base url to the client. If not set, the base url 90 | // is read from the OPENAI_BASE_URL environment variable. If still not set in ENV 91 | // VAR OPENAI_BASE_URL, then the default value is https://api.openai.com/v1 is used. 92 | func WithBaseURL(baseURL string) Option { 93 | return func(opts *options) { 94 | opts.baseURL = baseURL 95 | } 96 | } 97 | 98 | // WithOrganization passes the OpenAI organization to the client. If not set, the 99 | // organization is read from the OPENAI_ORGANIZATION. 100 | func WithOrganization(organization string) Option { 101 | return func(opts *options) { 102 | opts.organization = organization 103 | } 104 | } 105 | 106 | // WithAPIType passes the api type to the client. If not set, the default value 107 | // is APITypeOpenAI. 108 | func WithAPIType(apiType APIType) Option { 109 | return func(opts *options) { 110 | opts.apiType = apiType 111 | } 112 | } 113 | 114 | // WithAPIVersion passes the api version to the client. If not set, the default value 115 | // is DefaultAPIVersion. 116 | func WithAPIVersion(apiVersion string) Option { 117 | return func(opts *options) { 118 | opts.apiVersion = apiVersion 119 | } 120 | } 121 | 122 | // WithHTTPClient allows setting a custom HTTP client. If not set, the default value 123 | // is http.DefaultClient. 124 | func WithHTTPClient(client openaiclient.Doer) Option { 125 | return func(opts *options) { 126 | opts.httpClient = client 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /core/llms/openai/internal/openaiclient/openaiclient.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Travis Cline 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package openaiclient 22 | 23 | import ( 24 | "context" 25 | "errors" 26 | "fmt" 27 | "net/http" 28 | "strings" 29 | ) 30 | 31 | const ( 32 | defaultBaseURL = "https://api.openai.com/v1" 33 | defaultFunctionCallBehavior = "auto" 34 | ) 35 | 36 | // ErrEmptyResponse is returned when the OpenAI API returns an empty response. 37 | var ErrEmptyResponse = errors.New("empty response") 38 | 39 | type APIType string 40 | 41 | const ( 42 | APITypeOpenAI APIType = "OPEN_AI" 43 | APITypeAzure APIType = "AZURE" 44 | APITypeAzureAD APIType = "AZURE_AD" 45 | ) 46 | 47 | // Client is a client for the OpenAI API. 48 | type Client struct { 49 | token string 50 | Model string 51 | baseURL string 52 | organization string 53 | apiType APIType 54 | httpClient Doer 55 | 56 | EmbeddingModel string 57 | // required when APIType is APITypeAzure or APITypeAzureAD 58 | apiVersion string 59 | } 60 | 61 | // Option is an option for the OpenAI client. 62 | type Option func(*Client) error 63 | 64 | // Doer performs a HTTP request. 65 | type Doer interface { 66 | Do(req *http.Request) (*http.Response, error) 67 | } 68 | 69 | // New returns a new OpenAI client. 70 | func New(token string, model string, baseURL string, organization string, 71 | apiType APIType, apiVersion string, httpClient Doer, embeddingModel string, 72 | opts ...Option, 73 | ) (*Client, error) { 74 | c := &Client{ 75 | token: token, 76 | Model: model, 77 | EmbeddingModel: embeddingModel, 78 | baseURL: strings.TrimSuffix(baseURL, "/"), 79 | organization: organization, 80 | apiType: apiType, 81 | apiVersion: apiVersion, 82 | httpClient: httpClient, 83 | } 84 | 85 | for _, opt := range opts { 86 | if err := opt(c); err != nil { 87 | return nil, err 88 | } 89 | } 90 | 91 | return c, nil 92 | } 93 | 94 | // CreateChat creates chat request. 95 | func (c *Client) CreateChat(ctx context.Context, r *ChatRequest) (*ChatResponse, error) { 96 | if r.Model == "" { 97 | if c.Model == "" { 98 | r.Model = defaultChatModel 99 | } else { 100 | r.Model = c.Model 101 | } 102 | } 103 | if r.FunctionCallBehavior == "" && len(r.Functions) > 0 { 104 | r.FunctionCallBehavior = defaultFunctionCallBehavior 105 | } 106 | resp, err := c.createChat(ctx, r) 107 | if err != nil { 108 | return nil, err 109 | } 110 | if len(resp.Choices) == 0 { 111 | return nil, ErrEmptyResponse 112 | } 113 | return resp, nil 114 | } 115 | 116 | func IsAzure(apiType APIType) bool { 117 | return apiType == APITypeAzure || apiType == APITypeAzureAD 118 | } 119 | 120 | func (c *Client) setHeaders(req *http.Request) { 121 | req.Header.Set("Content-Type", "application/json") 122 | if c.apiType == APITypeOpenAI || c.apiType == APITypeAzureAD { 123 | req.Header.Set("Authorization", "Bearer "+c.token) 124 | } else { 125 | req.Header.Set("api-key", c.token) 126 | } 127 | if c.organization != "" { 128 | req.Header.Set("OpenAI-Organization", c.organization) 129 | } 130 | } 131 | 132 | func (c *Client) buildURL(suffix string, model string) string { 133 | if IsAzure(c.apiType) { 134 | return c.buildAzureURL(suffix, model) 135 | } 136 | 137 | // open ai implement: 138 | return fmt.Sprintf("%s%s", c.baseURL, suffix) 139 | } 140 | 141 | func (c *Client) buildAzureURL(suffix string, model string) string { 142 | baseURL := c.baseURL 143 | baseURL = strings.TrimRight(baseURL, "/") 144 | 145 | // azure example url: 146 | // /openai/deployments/{model}/chat/completions?api-version={api_version} 147 | return fmt.Sprintf("%s/openai/deployments/%s%s?api-version=%s", 148 | baseURL, model, suffix, c.apiVersion, 149 | ) 150 | } 151 | -------------------------------------------------------------------------------- /internal/dao/internal/p_chat_records.go: -------------------------------------------------------------------------------- 1 | // ========================================================================== 2 | // Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. 3 | // ========================================================================== 4 | 5 | package internal 6 | 7 | import ( 8 | "context" 9 | 10 | "github.com/gogf/gf/v2/database/gdb" 11 | "github.com/gogf/gf/v2/frame/g" 12 | ) 13 | 14 | // PChatRecordsDao is the data access object for table p_chat_records. 15 | type PChatRecordsDao struct { 16 | table string // table is the underlying table name of the DAO. 17 | group string // group is the database configuration group name of current DAO. 18 | columns PChatRecordsColumns // columns contains all the column names of Table for convenient usage. 19 | } 20 | 21 | // PChatRecordsColumns defines and stores column names for table p_chat_records. 22 | type PChatRecordsColumns struct { 23 | Id string // 序号 24 | ConversationUuid string // 会话 uuid 25 | CreateUser string // 创建人 26 | GitRepo string // git 仓库 27 | GitBranch string // git 分支 28 | CodePath string // 代码路径 29 | CodeLanguage string // 代码语言 30 | IdeInfo string // ide 版本信息 31 | ProjectName string // 插件名称 32 | ProjectVersion string // 插件版本 33 | EngineName string // 模型名称 34 | ModelName string // 模型名称 35 | ModelVersion string // 模型版本 36 | PromptTokens string // prompt tokens 37 | CompletionTokens string // 生成代码 tokens 38 | TotalTokens string // 总 tokens 39 | CompletionCodeLines string // 生成代码行数 40 | CompletionDuration string // 生成代码耗时 41 | FailureReason string // 会话失败原因 42 | AcceptStatus string // 会话是否被采纳 43 | Updatetime string // 更新时间 44 | Inserttime string // 插入时间 45 | Isactive string // 逻辑删除 46 | DeleteToken string // 逻辑删除标志 47 | ConversationType string // 会话类型 48 | PromptTmplVersion string // prompt 版本 49 | PromptTmplContent string // prompt 内容 50 | } 51 | 52 | // pChatRecordsColumns holds the columns for table p_chat_records. 53 | var pChatRecordsColumns = PChatRecordsColumns{ 54 | Id: "id", 55 | ConversationUuid: "conversation_uuid", 56 | CreateUser: "create_user", 57 | GitRepo: "git_repo", 58 | GitBranch: "git_branch", 59 | CodePath: "code_path", 60 | CodeLanguage: "code_language", 61 | IdeInfo: "ide_info", 62 | ProjectName: "project_name", 63 | ProjectVersion: "project_version", 64 | EngineName: "engine_name", 65 | ModelName: "model_name", 66 | ModelVersion: "model_version", 67 | PromptTokens: "prompt_tokens", 68 | CompletionTokens: "completion_tokens", 69 | TotalTokens: "total_tokens", 70 | CompletionCodeLines: "completion_code_lines", 71 | CompletionDuration: "completion_duration", 72 | FailureReason: "failure_reason", 73 | AcceptStatus: "accept_status", 74 | Updatetime: "updatetime", 75 | Inserttime: "inserttime", 76 | Isactive: "isactive", 77 | DeleteToken: "delete_token", 78 | ConversationType: "conversation_type", 79 | PromptTmplVersion: "prompt_tmpl_version", 80 | PromptTmplContent: "prompt_tmpl_content", 81 | } 82 | 83 | // NewPChatRecordsDao creates and returns a new DAO object for table data access. 84 | func NewPChatRecordsDao() *PChatRecordsDao { 85 | return &PChatRecordsDao{ 86 | group: "default", 87 | table: "p_chat_records", 88 | columns: pChatRecordsColumns, 89 | } 90 | } 91 | 92 | // DB retrieves and returns the underlying raw database management object of current DAO. 93 | func (dao *PChatRecordsDao) DB() gdb.DB { 94 | return g.DB(dao.group) 95 | } 96 | 97 | // Table returns the table name of current dao. 98 | func (dao *PChatRecordsDao) Table() string { 99 | return dao.table 100 | } 101 | 102 | // Columns returns all column names of current dao. 103 | func (dao *PChatRecordsDao) Columns() PChatRecordsColumns { 104 | return dao.columns 105 | } 106 | 107 | // Group returns the configuration group name of database of current dao. 108 | func (dao *PChatRecordsDao) Group() string { 109 | return dao.group 110 | } 111 | 112 | // Ctx creates and returns the Model for current DAO, It automatically sets the context for current operation. 113 | func (dao *PChatRecordsDao) Ctx(ctx context.Context) *gdb.Model { 114 | return dao.DB().Model(dao.table).Safe().Ctx(ctx) 115 | } 116 | 117 | // Transaction wraps the transaction logic using function f. 118 | // It rollbacks the transaction and returns the error from function f if it returns non-nil error. 119 | // It commits the transaction and returns nil if function f returns nil. 120 | // 121 | // Note that, you should not Commit or Rollback the transaction in function f 122 | // as it is automatically handled by this function. 123 | func (dao *PChatRecordsDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) { 124 | return dao.Ctx(ctx).Transaction(ctx, f) 125 | } 126 | -------------------------------------------------------------------------------- /core/callbacks/handler.go: -------------------------------------------------------------------------------- 1 | package callbacks 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/gogf/gf/v2/frame/g" 7 | "github.com/gogf/gf/v2/util/gconv" 8 | "strings" 9 | "time" 10 | "xcoder/core/llms" 11 | "xcoder/internal/dao/mongodb" 12 | "xcoder/internal/model/input/chatin" 13 | "xcoder/internal/model/input/codein" 14 | "xcoder/internal/service" 15 | "xcoder/utility/xconcurrent" 16 | ) 17 | 18 | type HandlerImpl struct{} 19 | 20 | func (h HandlerImpl) HandleLLMStart(ctx context.Context, prompts []string) { 21 | fmt.Println("Entering LLM with prompts:", prompts) 22 | } 23 | 24 | func (h HandlerImpl) HandleLLMEnd(ctx context.Context, output llms.LLMResult) { 25 | fmt.Println("Implement me") 26 | } 27 | 28 | func (h HandlerImpl) HandleLLMPredictEnd(ctx context.Context, output llms.LLMResult) error { 29 | completionCode := output.Generations[0].Text 30 | completionCodeLines := len(strings.SplitAfter(completionCode, "\n")) 31 | 32 | dataToMysql := &codein.CodeUpdateGenerationInfoReq{ 33 | GenerateUUID: output.Generations[0].GenerationInfo["generate_uuid"].(string), 34 | PromptTokens: output.Generations[0].GenerationInfo["prompt_tokens"].(int), 35 | CompletionCode: completionCode, 36 | CompletionCodeTokens: output.Generations[0].GenerationInfo["completion_code_tokens"].(int), 37 | CompletionCodeLines: completionCodeLines, 38 | CompletionDuration: output.Generations[0].GenerationInfo["completion_duration"].(int), 39 | FinishReason: output.Generations[0].GenerationInfo["finish_reason"].(string), 40 | FailureReason: output.Generations[0].GenerationInfo["failure_reason"].(string), 41 | } 42 | 43 | dataToMongoDB := &codein.CodeGenerateUpdateMongoRequest{ 44 | GenerateUUID: output.Generations[0].GenerationInfo["generate_uuid"].(string), 45 | RawCompletionCode: output.Generations[0].GenerationInfo["completion_code"].(string), 46 | CompletionCode: completionCode, 47 | } 48 | 49 | p := xconcurrent.NewBase("codeGenerateRecordUpdate") 50 | p.Compute(ctx, func(ctx context.Context) error { 51 | g.Log().Infof(ctx, "AsyncCodeGenerateUpdateDB start to update mysql") 52 | // 某些情况,insert 速度慢,等待1s后再次更新 53 | time.Sleep(1 * time.Second) 54 | 55 | // 请求字段保存到 mysql 56 | _, err := service.Code().UpdateGenerationInfo(ctx, dataToMysql) 57 | if err != nil { 58 | g.Log().Errorf(ctx, "Mysql CodeGenerateUpdate failed: %v", err) 59 | return err 60 | } 61 | 62 | g.Log().Infof(ctx, "AsyncCodeGenerateUpdateDB start to update mongodb") 63 | // 保存代码到 mongodb 64 | _, err = mongodb.MDao.CodeGenerateUpdate(ctx, dataToMongoDB) 65 | if err != nil { 66 | g.Log().Errorf(ctx, "Mongodb CodeGenerateUpdate failed: %v", err) 67 | return err 68 | } 69 | 70 | g.Log().Infof(ctx, "AsyncCodeGenerateUpdateDB finished") 71 | 72 | return nil 73 | }) 74 | 75 | return nil 76 | } 77 | 78 | func getGenerateCodeFromResponse(ctx context.Context, response string) string { 79 | // 获取代码 80 | completionCode := strings.Split(response, "```") 81 | if len(completionCode) < 2 { 82 | g.Log().Infof(ctx, "getGenerateCodeFromResponse no code in completion result: %v", response) 83 | return "" 84 | } 85 | 86 | var result string 87 | lines := strings.Split(completionCode[1], "\n") 88 | if len(lines) > 1 { 89 | result = strings.Join(lines[1:], "\n") 90 | } 91 | 92 | return result 93 | } 94 | 95 | func (h HandlerImpl) HandleLLMChatEnd(ctx context.Context, output llms.LLMResult) error { 96 | dataToMysql := &chatin.ChatMessageUpdateMysqlReq{ 97 | ConversationUUID: output.Generations[0].GenerationInfo["conversation_uuid"].(string), 98 | PromptTokens: output.Generations[0].GenerationInfo["prompt_tokens"].(int), 99 | CompletionTokens: output.Generations[0].GenerationInfo["completion_tokens"].(int), 100 | TotalTokens: output.Generations[0].GenerationInfo["total_tokens"].(int), 101 | CompletionDuration: gconv.Int(output.Generations[0].GenerationInfo["completion_duration"].(int64)), 102 | } 103 | 104 | completionCode := getGenerateCodeFromResponse(ctx, output.Generations[0].Text) 105 | 106 | dataToMongoDB := &chatin.ChatMessageUpdateMongoRequest{ 107 | ConversationUUID: output.Generations[0].GenerationInfo["conversation_uuid"].(string), 108 | Response: output.Generations[0].Text, 109 | CompletionCode: completionCode, 110 | } 111 | 112 | // 异步更新数据到 mysql 和 mongodb 113 | p := xconcurrent.NewBase("chatMessageUpdate") 114 | p.Compute(ctx, func(ctx context.Context) error { 115 | g.Log().Infof(ctx, "AsyncChatMessageUpdateDB start to update mysql") 116 | // 请求字段保存到 mysql 117 | _, err := service.Chat().UpdateChatRecordsFields(ctx, dataToMysql) 118 | if err != nil { 119 | g.Log().Errorf(ctx, "Mysql ChatMessageUpdate failed: %v", err) 120 | return err 121 | } 122 | 123 | g.Log().Infof(ctx, "AsyncChatMessageUpdateDB start to update mongodb") 124 | // 保存代码到 mongodb 125 | _, err = mongodb.MDao.ChatMessageUpdate(ctx, dataToMongoDB) 126 | if err != nil { 127 | g.Log().Errorf(ctx, "Mongodb ChatMessageUpdate failed: %v", err) 128 | return err 129 | } 130 | 131 | g.Log().Infof(ctx, "AsyncChatMessageUpdateDB finished") 132 | 133 | return nil 134 | }) 135 | 136 | return nil 137 | } 138 | -------------------------------------------------------------------------------- /internal/dao/internal/p_code_gen_records.go: -------------------------------------------------------------------------------- 1 | // ========================================================================== 2 | // Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. 3 | // ========================================================================== 4 | 5 | package internal 6 | 7 | import ( 8 | "context" 9 | 10 | "github.com/gogf/gf/v2/database/gdb" 11 | "github.com/gogf/gf/v2/frame/g" 12 | ) 13 | 14 | // PCodeGenRecordsDao is the data access object for table p_code_gen_records. 15 | type PCodeGenRecordsDao struct { 16 | table string // table is the underlying table name of the DAO. 17 | group string // group is the database configuration group name of current DAO. 18 | columns PCodeGenRecordsColumns // columns contains all the column names of Table for convenient usage. 19 | } 20 | 21 | // PCodeGenRecordsColumns defines and stores column names for table p_code_gen_records. 22 | type PCodeGenRecordsColumns struct { 23 | Id string // 24 | GenerateUuid string // 25 | GenerateType string // 26 | IsSingleLine string // 27 | CreateUser string // 28 | GitRepo string // 29 | GitBranch string // 30 | CodePath string // 31 | CodeLanguage string // 32 | IdeInfo string // 33 | StartCursorIdx string // 34 | PrefixCodeTokens string // 35 | SuffixCodeTokens string // 36 | ModelName string // 37 | ModelVersion string // 38 | PromptTokens string // 39 | CompletionCode string // 40 | CompletionDuration string // 41 | CompletionCodeTokens string // 42 | CompletionCodeLines string // 43 | FinishReason string // 44 | FailureReason string // 45 | Updatetime string // 46 | Inserttime string // 47 | Isactive string // 48 | DeleteToken string // 49 | AcceptStatus string // 50 | CodeTotalLines string // 51 | CrossfileCtxNums string // 52 | } 53 | 54 | // pCodeGenRecordsColumns holds the columns for table p_code_gen_records. 55 | var pCodeGenRecordsColumns = PCodeGenRecordsColumns{ 56 | Id: "id", 57 | GenerateUuid: "generate_uuid", 58 | GenerateType: "generate_type", 59 | IsSingleLine: "is_single_line", 60 | CreateUser: "create_user", 61 | GitRepo: "git_repo", 62 | GitBranch: "git_branch", 63 | CodePath: "code_path", 64 | CodeLanguage: "code_language", 65 | IdeInfo: "ide_info", 66 | StartCursorIdx: "start_cursor_idx", 67 | PrefixCodeTokens: "prefix_code_tokens", 68 | SuffixCodeTokens: "suffix_code_tokens", 69 | ModelName: "model_name", 70 | ModelVersion: "model_version", 71 | PromptTokens: "prompt_tokens", 72 | CompletionCode: "completion_code", 73 | CompletionDuration: "completion_duration", 74 | CompletionCodeTokens: "completion_code_tokens", 75 | CompletionCodeLines: "completion_code_lines", 76 | FinishReason: "finish_reason", 77 | FailureReason: "failure_reason", 78 | Updatetime: "updatetime", 79 | Inserttime: "inserttime", 80 | Isactive: "isactive", 81 | DeleteToken: "delete_token", 82 | AcceptStatus: "accept_status", 83 | CodeTotalLines: "code_total_lines", 84 | CrossfileCtxNums: "crossfile_ctx_nums", 85 | } 86 | 87 | // NewPCodeGenRecordsDao creates and returns a new DAO object for table data access. 88 | func NewPCodeGenRecordsDao() *PCodeGenRecordsDao { 89 | return &PCodeGenRecordsDao{ 90 | group: "default", 91 | table: "p_code_gen_records", 92 | columns: pCodeGenRecordsColumns, 93 | } 94 | } 95 | 96 | // DB retrieves and returns the underlying raw database management object of current DAO. 97 | func (dao *PCodeGenRecordsDao) DB() gdb.DB { 98 | return g.DB(dao.group) 99 | } 100 | 101 | // Table returns the table name of current dao. 102 | func (dao *PCodeGenRecordsDao) Table() string { 103 | return dao.table 104 | } 105 | 106 | // Columns returns all column names of current dao. 107 | func (dao *PCodeGenRecordsDao) Columns() PCodeGenRecordsColumns { 108 | return dao.columns 109 | } 110 | 111 | // Group returns the configuration group name of database of current dao. 112 | func (dao *PCodeGenRecordsDao) Group() string { 113 | return dao.group 114 | } 115 | 116 | // Ctx creates and returns the Model for current DAO, It automatically sets the context for current operation. 117 | func (dao *PCodeGenRecordsDao) Ctx(ctx context.Context) *gdb.Model { 118 | return dao.DB().Model(dao.table).Safe().Ctx(ctx) 119 | } 120 | 121 | // Transaction wraps the transaction logic using function f. 122 | // It rollbacks the transaction and returns the error from function f if it returns non-nil error. 123 | // It commits the transaction and returns nil if function f returns nil. 124 | // 125 | // Note that, you should not Commit or Rollback the transaction in function f 126 | // as it is automatically handled by this function. 127 | func (dao *PCodeGenRecordsDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) { 128 | return dao.Ctx(ctx).Transaction(ctx, f) 129 | } 130 | -------------------------------------------------------------------------------- /docs/development.md: -------------------------------------------------------------------------------- 1 | # XCoder 2 | XCoder 是一款旨在提高开发效率的工具,它能够根据用户的需求和输入,自动生成相应的代码片段或完整的代码文件。该插件旨在集成到主流的代码编辑器中,为开发者提供智能的代码辅助功能,帮助他们提高开发效率、减少错误,并提升代码质量。 3 | 4 | ## 项目配置 5 | 项目配置主要是项目的一些组件配置,mysql,mongodb 等,可以直接使用项目默认的配置,也可以换成自己的数据库。 6 | 7 | ### 使用项目默认配置 8 | * 项目使用 docker-compose 进行部署,部署文件参考:[`manifest/deploy/docker-compose/docker-compose.yml`](../manifest/deploy/docker-compose/docker-compose.yml),包括:mysql,mongodb,xcoder 等服务。 9 | * mysql,mongodb 在 `docker-compose.yml` 中设置了初始用户,密码及数据库名称,用于初始创建库表的配置。 10 | * 也支持修改这些数据库的默认配置,如果修改了,也需要在文件 [`manifest/config/config.yaml`](../manifest/config/config.yaml) 中同步修改,这个文件用于配置数据库的连接信息。 11 | 12 | ### 使用自己的数据库 13 | 1. 直接在 [`manifest/config/config.yaml`](../manifest/config/config.yaml) 中对应的 mysql,mongodb 部分修改成自己的数据库连接即可。 14 | 2. 然后需要执行 [`manifest/deploy/initdb.d/init_mysql.sql`](../manifest/deploy/initdb.d/init_mysql.sql) 脚本,对 mysql 数据库表进行初始化创建操作。 15 | 16 | ## 项目部署 17 | ### 部署依赖 18 | * docker 19 | * docker-compose 20 | * golang 1.18+ 21 | 22 | ### 镜像构建 23 | git clone 下载项目之后,执行以下命令行,即可构建项目镜像: 24 | ```shell 25 | $ cd XCoder-Server 26 | 27 | $ docker build -t xcoderai/xcoder:v0.1.0 . 28 | ``` 29 | 30 | 当然也可以直接使用 [`manifest/deploy/docker-compose/docker-compose.yml`](../manifest/deploy/docker-compose/docker-compose.yml) 中,我们构建好的 xcoder 官方镜像。 31 | 32 | ### 项目参数配置 33 | 项目参数配置文件,位于 `manifest/config/` 目录,包括以下两个文件: 34 | * [xcoder_code_generate_llm_params_conf.json](../manifest/config/xcoder_code_generate_llm_params_conf.json): 代码生成大模型参数相关配置文件 35 | * [xcoder_chat_llm_params_conf.json](../manifest/config/xcoder_chat_llm_params_conf.json): 智能聊天(chat,单元测试,代码编辑等)模块大模型参数相关配置文件 36 | 37 | **配置文件具体参数:** 38 | #### xcoder_code_generate_llm_params_conf.json 39 | 40 | ```json 41 | { 42 | "config": { 43 | "codellama": { 44 | "modelName": "codellama", 45 | "modelVersion": "codellama-13b-hf", 46 | "totalMaxTokens": 2500, 47 | "singleLineMaxTokens": 80, 48 | "multiLineMaxTokens": 160, 49 | "singeLineTemperature": 0.2, 50 | "multiLineTemperature": 0, 51 | "singleLineStopWords": ["\n", "\r\n"], 52 | "multiLineStopWords": ["\n\n", "\r\n\r\n"], 53 | "singleLineTopP": 1, 54 | "multiLineTopP": 1, 55 | "connUrls": [] 56 | }, 57 | "deepseeker": { 58 | "modelName": "deepseeker", 59 | "modelVersion": "deepseek-coder-7b-base", 60 | "totalMaxTokens": 2500, 61 | "singleLineMaxTokens": 80, 62 | "multiLineMaxTokens": 160, 63 | "singeLineTemperature": 0.2, 64 | "multiLineTemperature": 0, 65 | "singleLineStopWords": ["\n", "\r\n"], 66 | "multiLineStopWords": ["\n\n", "\r\n\r\n"], 67 | "singleLineTopP": 1, 68 | "multiLineTopP": 1, 69 | "connUrls": [] 70 | } 71 | }, 72 | "selectedModel": "codellama" 73 | } 74 | ``` 75 | **参数解释:** 76 | - **modelName**:string, llm 大模型的名称。 77 | - **modelVersion**:string, llm 大模型的版本。此项需要跟后面本地大模型部署的 llm 名称保持一致。 78 | - **singleLineMaxTokens**:int, 单行代码生成的最大 token 数。 79 | - **multiLineMaxTokens**:int, 多行代码生成的最大 token 数。 80 | - **singleLineStopWords**:[]string{}, 单行代码生成的停止符。 81 | - **multiLineStopWords**:[]string{}, 多行代码生成的停止符。 82 | - **connUrls**:[]string, 大模型的连接地址,支持配置多个。 83 | - **selectedModel**:string, 设置使用的模型名称,例如:设置了 codellama,则使用 codellama 模型生成代码。 84 | 85 | #### xcoder_chat_llm_params_conf.json 86 | 87 | ```json 88 | { 89 | "config": { 90 | "llmType": "", 91 | "llmParams": { 92 | "apiBase": "", 93 | "apiKey": "", 94 | "apiVersion": "", 95 | "model": "", 96 | "temperature": 0.1, 97 | "maxTokens": 2500, 98 | "topP": 1, 99 | "frequencyPenalty": 0, 100 | "presencePenalty": 0 101 | } 102 | } 103 | } 104 | ``` 105 | **参数解释:** 106 | 1. **llmType**: string,用于指定语言模型的类型。目前支持 openai,azure. 107 | 2. **llmParams**: map,包含用于配置语言模型的具体参数。 108 | - **apiBase**: string,llm 服务的访问地址。如果是 openai 类型,支持官方模型,末尾需要带上版本号。也支持自己本地部署的模型,这时需要自行确定末尾是否需要带上版本号。 109 | - **apiKey**: string,访问 llm 服务的 api key。 110 | - **apiVersion**: string,API的版本号,用于指定使用的API版本。如果是 openai 类型,此项可留空。 111 | - **model**: string,llm 模型名称。 112 | - **temperature**: float,控制生成文本的随机性。值越高,生成的文本越随机;值越低,生成的文本越确定。默认值是 `0.1`。 113 | - **maxTokens**: int,指定生成文本的最大长度,以令牌(tokens)为单位。默认值是 `2500`。 114 | - **topP**: float,用于控制核采样(nucleus sampling)的参数,影响生成文本的多样性。默认值是 `1`,意味着使用全部可能的令牌。 115 | - **frequencyPenalty**: int,用于减少重复内容的生成。值越高,重复内容出现的可能性越低。默认值是 `0`。 116 | - **presencePenalty**:int,用于增加生成文本中新内容的比例。值越高,新内容出现的可能性越高。默认值是 `0`。 117 | 118 | 119 | ### 项目启动 120 | 如果自己在上一步中,构建了镜像,则在 `docker-compose.yml` 中,替换成自己的 xcoder 镜像。然后执行以下命令进行项目启动: 121 | ```shell 122 | $ cd manifest/deploy/docker-compose 123 | $ docker-compose -f docker-compose.yml up 124 | ``` 125 | 126 | 127 | ## 大模型部署 128 | ### 镜像构建 129 | 这里可以构建自己的 vllm 镜像,也可以直接使用官方的 vllm 镜像。如果要使用自己的,则使用以下命令进行构建: 130 | ```shell 131 | $ cd llm_deploy/ 132 | 133 | $ docker build -t vllm/vllm:v1.0.0 . 134 | ``` 135 | 136 | ### codellama 137 | 部署 codellama 代码大模型,相关配置信息都在 docker-compose.codellama.yaml 中配置好了,直接使用以下命令部署即可: 138 | ```shell 139 | $ cd llm_deploy/ 140 | $ docker-compose -f docker-compose.codellama.yaml up -d 141 | ``` 142 | 143 | ### deepseeker 144 | 部署 deepseeker 代码大模型,相关配置信息都在 docker-compose.deepseeker.yaml 中配置好了,直接使用以下命令部署即可: 145 | ```shell 146 | $ cd llm_deploy/ 147 | $ docker-compose -f docker-composer.deepseeker.yaml up -d 148 | ``` -------------------------------------------------------------------------------- /utility/xutils/xutils.go: -------------------------------------------------------------------------------- 1 | package xutils 2 | 3 | import ( 4 | "context" 5 | "github.com/gogf/gf/v2/frame/g" 6 | "os" 7 | "strings" 8 | "time" 9 | ) 10 | 11 | // CodeLanguageToLower 12 | // 将 code language 转换为小写,并将 go 转换为 golang 13 | func CodeLanguageToLower(codeLanguage string) string { 14 | codeLanguage = strings.ToLower(codeLanguage) 15 | if codeLanguage == "go" { 16 | codeLanguage = "golang" 17 | } 18 | return codeLanguage 19 | } 20 | 21 | func CodeLanguageAnnotationFlagGet(codeLanguage string) string { 22 | var codeLanguageAnnotationFlagMap = map[string]string{ 23 | "golang": "//", 24 | "java": "//", 25 | "python": "#", 26 | "javascript": "//", 27 | "typescript": "//", 28 | "c": "//", 29 | "cpp": "//", 30 | "csharp": "//", 31 | "php": "//", 32 | "ruby": "#", 33 | "rust": "//", 34 | "scala": "//", 35 | "swift": "//", 36 | "kotlin": "//", 37 | "groovy": "//", 38 | "dart": "//", 39 | "elixir": "#", 40 | "haskell": "--", 41 | "erlang": "%%", 42 | "fortran": "!", 43 | } 44 | 45 | if val, ok := codeLanguageAnnotationFlagMap[codeLanguage]; ok { 46 | return val 47 | } else { 48 | return "//" 49 | } 50 | } 51 | 52 | // GetEnv 53 | // 获取环境变量,如果没有则返回默认值 54 | func GetEnv(key string, defaultValue string) string { 55 | value := os.Getenv(key) 56 | if value == "" { 57 | return defaultValue 58 | } 59 | return value 60 | } 61 | 62 | // StringIsInSmallSlice 判断字符串是否在字符串数组中 63 | func StringIsInSmallSlice(target string, elements []string) bool { 64 | for _, s := range elements { 65 | if s == target { 66 | return true 67 | } 68 | } 69 | return false 70 | } 71 | 72 | // JudgeSliceIsEq 73 | // 判断字符串数组是否相等 74 | func JudgeSliceIsEq(a, b []string) bool { 75 | // If one is nil, the other must also be nil. 76 | if (a == nil) != (b == nil) { 77 | return false 78 | } 79 | 80 | if len(a) != len(b) { 81 | return false 82 | } 83 | 84 | for i := range a { 85 | if a[i] != b[i] { 86 | return false 87 | } 88 | } 89 | 90 | return true 91 | } 92 | 93 | // FirstNoEmptyLineIdx 94 | // 获取字符串中不是空行的第一行索引 95 | func FirstNoEmptyLineIdx(lines []string) int { 96 | var idx int 97 | for i, line := range lines { 98 | if strings.TrimSpace(line) != "" { 99 | idx = i 100 | break 101 | } 102 | } 103 | 104 | return idx 105 | } 106 | 107 | // TrimSlices 108 | // 去掉字符串数组中,每行首尾的 \t 或空格 109 | func TrimSlices(lines []string) []string { 110 | var res []string 111 | for _, line := range lines { 112 | res = append(res, strings.Trim(line, "\t ")) 113 | } 114 | 115 | return res 116 | } 117 | 118 | func findESIdx(ctx context.Context, gLines []string, sLines []string) int { 119 | gIdx := len(gLines) - 1 120 | if len(sLines) == 0 { 121 | return gIdx + 1 122 | } 123 | 124 | //multiLinesNeedCheckMoreFlags := xapollo.ShortCutParamsConfigInfo.MultiLineNeedCheckMoreFlags 125 | multiLinesNeedCheckMoreFlags := []string{"}", "]"} 126 | 127 | // 获取去掉 \t 或空格后的字符串数组 128 | gCLines := TrimSlices(gLines) 129 | sCLines := TrimSlices(sLines) 130 | 131 | // 获取不是空行的第一行,作为开始比较的行 132 | gSIdx := FirstNoEmptyLineIdx(gCLines) 133 | sSIdx := FirstNoEmptyLineIdx(sCLines) 134 | 135 | firstNoEmptyLine := sCLines[sSIdx] 136 | g.Log().Infof(ctx, "firstNoEmptyLine: %s", firstNoEmptyLine) 137 | for i := gSIdx; i <= len(gCLines)-1; i++ { 138 | if gCLines[i] == firstNoEmptyLine { 139 | // 白名单里,当前行相等,存在下一行不相等的话,eIdx 取全;否则,返回当前行的上一行 140 | if StringIsInSmallSlice(firstNoEmptyLine, multiLinesNeedCheckMoreFlags) { 141 | needCheckLines := 1 142 | if len(gCLines) >= i+needCheckLines+1 && len(sCLines) >= sSIdx+needCheckLines+1 { 143 | if !JudgeSliceIsEq(gCLines[i+1:i+needCheckLines+1], sCLines[sSIdx+1:sSIdx+needCheckLines+1]) { 144 | gIdx = gIdx 145 | } else { 146 | // 返回当前行的上一行 147 | gIdx = i - 1 148 | break 149 | } 150 | } 151 | // 相等的符号在最后一行,直接返回当前行的上一行 152 | if len(gCLines) == i+1 { 153 | gIdx = i - 1 154 | break 155 | } 156 | } else { 157 | // 非白名单里,当前行相等,直接返回当前行的上一行 158 | gIdx = i - 1 159 | break 160 | } 161 | } 162 | } 163 | 164 | return gIdx + 1 165 | } 166 | 167 | func HandleRemoveDuplicateCodeForMultiLine(ctx context.Context, generateCode string, suffixCode string) string { 168 | g.Log().Infof(ctx, "RemoveDuplicateCodeForMultiLine start ...") 169 | start := time.Now() 170 | 171 | gLines := strings.Split(generateCode, "\n") 172 | sLines := strings.Split(suffixCode, "\n") 173 | 174 | eIdx := findESIdx(ctx, gLines, sLines) 175 | 176 | duration := time.Since(start).Milliseconds() 177 | resp := strings.Join(gLines[:eIdx], "\n") 178 | 179 | g.Log().Infof(ctx, "RemoveDuplicateCodeForMultiLine end duration: %d ms, code: %s", duration, resp) 180 | 181 | return resp 182 | } 183 | 184 | func HandleGenerateTextWithLengthStop(ctx context.Context, s string) string { 185 | g.Log().Infof(ctx, "handleGenerateTextWithLengthStop with string: %s", s) 186 | lines := strings.Split(s, "\n") 187 | if len(lines) > 1 { 188 | lines = lines[:len(lines)-1] 189 | return strings.Join(lines, "\n") 190 | } 191 | return s 192 | } 193 | 194 | func HandleRemoveTailSpaceIfExistForSingleLine(ctx context.Context, s string) string { 195 | g.Log().Infof(ctx, "handleRemoveTailSpaceIfExistForSingleLine with string: %s", s) 196 | return strings.TrimRight(s, " ") 197 | } 198 | --------------------------------------------------------------------------------