Compare commits
9 Commits
de68151539
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 70fad51f40 | |||
| 6764684fe7 | |||
| da2c296c05 | |||
| bdf74652b3 | |||
| 70e8627a96 | |||
| c2c6577064 | |||
| 260c7d054c | |||
| d25933cf26 | |||
| 4aa56441ce |
9
Makefile
9
Makefile
@@ -10,10 +10,15 @@ help:
|
|||||||
@echo " build Build the application"
|
@echo " build Build the application"
|
||||||
@echo " clean Clean generated files"
|
@echo " clean Clean generated files"
|
||||||
@echo " test Run all tests"
|
@echo " test Run all tests"
|
||||||
@echo " swag Generate swagger docs"
|
@echo " swag Generate Swagger docs"
|
||||||
@echo " help Show this help message"
|
|
||||||
@echo " proto Generate protobuf files"
|
@echo " proto Generate protobuf files"
|
||||||
@echo " lint Lint the code"
|
@echo " lint Lint the code"
|
||||||
|
@echo " dev Run in development mode with hot-reload"
|
||||||
|
@echo " mcp-chrome Start the Google Chrome MCP server"
|
||||||
|
@echo " mcp-pgsql Start the PostgreSQL MCP server"
|
||||||
|
@echo " tree Generate the project file structure list"
|
||||||
|
@echo " gemini Start the gemini-cli"
|
||||||
|
@echo " help Show this help message"
|
||||||
|
|
||||||
# 运行应用
|
# 运行应用
|
||||||
.PHONY: run
|
.PHONY: run
|
||||||
|
|||||||
@@ -125,3 +125,11 @@ alarm_notification:
|
|||||||
dpanic: 1
|
dpanic: 1
|
||||||
panic: 1
|
panic: 1
|
||||||
fatal: 1
|
fatal: 1
|
||||||
|
|
||||||
|
# AI 服务配置
|
||||||
|
ai:
|
||||||
|
model: "gemini" # 不指定就是不用AI
|
||||||
|
gemini:
|
||||||
|
api_key: "YOUR_GEMINI_API_KEY" # 替换为你的 Gemini API Key
|
||||||
|
model_name: "gemini-2.5-flash" # Gemini 模型名称,例如 "gemini-pro"
|
||||||
|
timeout: 30 # AI 请求超时时间 (秒)
|
||||||
@@ -103,3 +103,11 @@ alarm_notification:
|
|||||||
dpanic: 1
|
dpanic: 1
|
||||||
panic: 1
|
panic: 1
|
||||||
fatal: 1
|
fatal: 1
|
||||||
|
|
||||||
|
# AI 服务配置
|
||||||
|
ai:
|
||||||
|
model: Gemini
|
||||||
|
gemini:
|
||||||
|
api_key: "AIzaSyAJdXUmoN07LIswDac6YxPeRnvXlR73OO8" # 替换为你的 Gemini API Key
|
||||||
|
model_name: "gemini-2.0-flash" # Gemini 模型名称,例如 "gemini-pro"
|
||||||
|
timeout: 30 # AI 请求超时时间 (秒)
|
||||||
|
|||||||
@@ -46,6 +46,20 @@ http://git.huangwc.com/pig/pig-farm-controller/issues/66
|
|||||||
7. 简单查看功能
|
7. 简单查看功能
|
||||||
- 两个配方对比页面(营养+成本对比)
|
- 两个配方对比页面(营养+成本对比)
|
||||||
|
|
||||||
|
# 实现总结
|
||||||
|
|
||||||
|
## 实现内容
|
||||||
|
|
||||||
|
实现库存和原料和营养和猪营养需求的管理, 支持根据库存和已录入原料和猪营养需求生成配方
|
||||||
|
|
||||||
|
## TODO
|
||||||
|
|
||||||
|
1. 发酵料管理考虑到发酵目前没有自动化流程, 不好追踪, 遂暂时不做
|
||||||
|
2. 目前的价格是根据原料的参考价设置的, 后续应当实现一个在服务供平台采集参考价, 以及使用原料采购价计算
|
||||||
|
3. 原料应该加上膨润土等, 比如膨润土的黄曲霉素含量应该是负数以表示减少饲料里的含量
|
||||||
|
4. 饲料保质期考虑到批次间管理暂时不方便, 等可以实现同一原料先进先出后再实现
|
||||||
|
5. 暂时不支持指定原料列表然后自动生成, 也不支持告诉用户当前生成不出是为什么, 等以后再做
|
||||||
|
|
||||||
# 完成事项
|
# 完成事项
|
||||||
|
|
||||||
1. 定义原料表, 营养表, 原料营养表, 原料库存变更表
|
1. 定义原料表, 营养表, 原料营养表, 原料库存变更表
|
||||||
79
docs/docs.go
79
docs/docs.go
@@ -3371,6 +3371,59 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/v1/feed/recipes/{id}/ai-diagnose": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "使用AI对指定配方进行点评,并针对目标猪类型给出建议。",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"饲料管理-配方"
|
||||||
|
],
|
||||||
|
"summary": "AI点评配方",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "配方ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "猪类型ID",
|
||||||
|
"name": "pig_type_id",
|
||||||
|
"in": "query",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "业务码为200代表AI点评成功",
|
||||||
|
"schema": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/controller.Response"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {
|
||||||
|
"$ref": "#/definitions/dto.ReviewRecipeResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/v1/inventory/stock/adjust": {
|
"/api/v1/inventory/stock/adjust": {
|
||||||
"post": {
|
"post": {
|
||||||
"security": [
|
"security": [
|
||||||
@@ -9165,6 +9218,23 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"dto.ReviewRecipeResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"ai_model": {
|
||||||
|
"description": "使用的 AI 模型",
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/models.AIModel"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"review_message": {
|
||||||
|
"description": "点评内容",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"dto.SellPigsRequest": {
|
"dto.SellPigsRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
@@ -10084,6 +10154,15 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"models.AIModel": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"Gemini"
|
||||||
|
],
|
||||||
|
"x-enum-varnames": [
|
||||||
|
"AI_MODEL_GEMINI"
|
||||||
|
]
|
||||||
|
},
|
||||||
"models.AlarmCode": {
|
"models.AlarmCode": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
|
|||||||
@@ -3363,6 +3363,59 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/v1/feed/recipes/{id}/ai-diagnose": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "使用AI对指定配方进行点评,并针对目标猪类型给出建议。",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"饲料管理-配方"
|
||||||
|
],
|
||||||
|
"summary": "AI点评配方",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "配方ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "猪类型ID",
|
||||||
|
"name": "pig_type_id",
|
||||||
|
"in": "query",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "业务码为200代表AI点评成功",
|
||||||
|
"schema": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/controller.Response"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {
|
||||||
|
"$ref": "#/definitions/dto.ReviewRecipeResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/v1/inventory/stock/adjust": {
|
"/api/v1/inventory/stock/adjust": {
|
||||||
"post": {
|
"post": {
|
||||||
"security": [
|
"security": [
|
||||||
@@ -9157,6 +9210,23 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"dto.ReviewRecipeResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"ai_model": {
|
||||||
|
"description": "使用的 AI 模型",
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/models.AIModel"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"review_message": {
|
||||||
|
"description": "点评内容",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"dto.SellPigsRequest": {
|
"dto.SellPigsRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
@@ -10076,6 +10146,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"models.AIModel": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"Gemini"
|
||||||
|
],
|
||||||
|
"x-enum-varnames": [
|
||||||
|
"AI_MODEL_GEMINI"
|
||||||
|
]
|
||||||
|
},
|
||||||
"models.AlarmCode": {
|
"models.AlarmCode": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
|
|||||||
@@ -1648,6 +1648,16 @@ definitions:
|
|||||||
- quantity
|
- quantity
|
||||||
- treatment_location
|
- treatment_location
|
||||||
type: object
|
type: object
|
||||||
|
dto.ReviewRecipeResponse:
|
||||||
|
properties:
|
||||||
|
ai_model:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/definitions/models.AIModel'
|
||||||
|
description: 使用的 AI 模型
|
||||||
|
review_message:
|
||||||
|
description: 点评内容
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
dto.SellPigsRequest:
|
dto.SellPigsRequest:
|
||||||
properties:
|
properties:
|
||||||
pen_id:
|
pen_id:
|
||||||
@@ -2274,6 +2284,12 @@ definitions:
|
|||||||
weight:
|
weight:
|
||||||
type: number
|
type: number
|
||||||
type: object
|
type: object
|
||||||
|
models.AIModel:
|
||||||
|
enum:
|
||||||
|
- Gemini
|
||||||
|
type: string
|
||||||
|
x-enum-varnames:
|
||||||
|
- AI_MODEL_GEMINI
|
||||||
models.AlarmCode:
|
models.AlarmCode:
|
||||||
enum:
|
enum:
|
||||||
- 温度阈值
|
- 温度阈值
|
||||||
@@ -4755,6 +4771,37 @@ paths:
|
|||||||
summary: 更新配方
|
summary: 更新配方
|
||||||
tags:
|
tags:
|
||||||
- 饲料管理-配方
|
- 饲料管理-配方
|
||||||
|
/api/v1/feed/recipes/{id}/ai-diagnose:
|
||||||
|
get:
|
||||||
|
description: 使用AI对指定配方进行点评,并针对目标猪类型给出建议。
|
||||||
|
parameters:
|
||||||
|
- description: 配方ID
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
- description: 猪类型ID
|
||||||
|
in: query
|
||||||
|
name: pig_type_id
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: 业务码为200代表AI点评成功
|
||||||
|
schema:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/definitions/controller.Response'
|
||||||
|
- properties:
|
||||||
|
data:
|
||||||
|
$ref: '#/definitions/dto.ReviewRecipeResponse'
|
||||||
|
type: object
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
summary: AI点评配方
|
||||||
|
tags:
|
||||||
|
- 饲料管理-配方
|
||||||
/api/v1/feed/recipes/generate-from-all-materials/{pig_type_id}:
|
/api/v1/feed/recipes/generate-from-all-materials/{pig_type_id}:
|
||||||
post:
|
post:
|
||||||
description: 根据指定的猪类型ID,使用系统中所有可用的原料,自动计算并创建一个成本最优的配方。
|
description: 根据指定的猪类型ID,使用系统中所有可用的原料,自动计算并创建一个成本最优的配方。
|
||||||
|
|||||||
34
go.mod
34
go.mod
@@ -9,6 +9,7 @@ require (
|
|||||||
github.com/go-openapi/swag v0.25.1
|
github.com/go-openapi/swag v0.25.1
|
||||||
github.com/go-openapi/validate v0.24.0
|
github.com/go-openapi/validate v0.24.0
|
||||||
github.com/golang-jwt/jwt/v5 v5.3.0
|
github.com/golang-jwt/jwt/v5 v5.3.0
|
||||||
|
github.com/google/generative-ai-go v0.20.1
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/labstack/echo/v4 v4.13.4
|
github.com/labstack/echo/v4 v4.13.4
|
||||||
github.com/panjf2000/ants/v2 v2.11.3
|
github.com/panjf2000/ants/v2 v2.11.3
|
||||||
@@ -20,7 +21,8 @@ require (
|
|||||||
go.uber.org/zap v1.27.0
|
go.uber.org/zap v1.27.0
|
||||||
golang.org/x/crypto v0.43.0
|
golang.org/x/crypto v0.43.0
|
||||||
gonum.org/v1/gonum v0.16.0
|
gonum.org/v1/gonum v0.16.0
|
||||||
google.golang.org/protobuf v1.36.9
|
google.golang.org/api v0.256.0
|
||||||
|
google.golang.org/protobuf v1.36.10
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||||
gopkg.in/yaml.v2 v2.4.0
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
gorm.io/datatypes v1.2.6
|
gorm.io/datatypes v1.2.6
|
||||||
@@ -29,11 +31,18 @@ require (
|
|||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
cloud.google.com/go v0.115.0 // indirect
|
||||||
|
cloud.google.com/go/ai v0.8.0 // indirect
|
||||||
|
cloud.google.com/go/auth v0.17.0 // indirect
|
||||||
|
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
|
||||||
|
cloud.google.com/go/compute/metadata v0.9.0 // indirect
|
||||||
|
cloud.google.com/go/longrunning v0.5.7 // indirect
|
||||||
filippo.io/edwards25519 v1.1.0 // indirect
|
filippo.io/edwards25519 v1.1.0 // indirect
|
||||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
|
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
|
||||||
|
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||||
github.com/ghodss/yaml v1.0.0 // indirect
|
github.com/ghodss/yaml v1.0.0 // indirect
|
||||||
github.com/go-logr/logr v1.4.1 // indirect
|
github.com/go-logr/logr v1.4.3 // indirect
|
||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
github.com/go-openapi/analysis v0.23.0 // indirect
|
github.com/go-openapi/analysis v0.23.0 // indirect
|
||||||
github.com/go-openapi/jsonpointer v0.22.1 // indirect
|
github.com/go-openapi/jsonpointer v0.22.1 // indirect
|
||||||
@@ -52,7 +61,9 @@ require (
|
|||||||
github.com/go-openapi/swag/typeutils v0.25.1 // indirect
|
github.com/go-openapi/swag/typeutils v0.25.1 // indirect
|
||||||
github.com/go-openapi/swag/yamlutils v0.25.1 // indirect
|
github.com/go-openapi/swag/yamlutils v0.25.1 // indirect
|
||||||
github.com/go-sql-driver/mysql v1.8.1 // indirect
|
github.com/go-sql-driver/mysql v1.8.1 // indirect
|
||||||
github.com/google/go-cmp v0.7.0 // indirect
|
github.com/google/s2a-go v0.1.9 // indirect
|
||||||
|
github.com/googleapis/enterprise-certificate-proxy v0.3.7 // indirect
|
||||||
|
github.com/googleapis/gax-go/v2 v2.15.0 // indirect
|
||||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||||
github.com/jackc/pgx/v5 v5.6.0 // indirect
|
github.com/jackc/pgx/v5 v5.6.0 // indirect
|
||||||
@@ -72,18 +83,25 @@ require (
|
|||||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
github.com/valyala/fasttemplate v1.2.2 // indirect
|
github.com/valyala/fasttemplate v1.2.2 // indirect
|
||||||
go.mongodb.org/mongo-driver v1.14.0 // indirect
|
go.mongodb.org/mongo-driver v1.14.0 // indirect
|
||||||
go.opentelemetry.io/otel v1.24.0 // indirect
|
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||||
go.opentelemetry.io/otel/metric v1.24.0 // indirect
|
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect
|
||||||
go.opentelemetry.io/otel/trace v1.24.0 // indirect
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
|
||||||
|
go.opentelemetry.io/otel v1.37.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/metric v1.37.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/trace v1.37.0 // indirect
|
||||||
go.uber.org/multierr v1.10.0 // indirect
|
go.uber.org/multierr v1.10.0 // indirect
|
||||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||||
golang.org/x/mod v0.29.0 // indirect
|
golang.org/x/mod v0.29.0 // indirect
|
||||||
golang.org/x/net v0.46.0 // indirect
|
golang.org/x/net v0.46.0 // indirect
|
||||||
golang.org/x/sync v0.17.0 // indirect
|
golang.org/x/oauth2 v0.33.0 // indirect
|
||||||
|
golang.org/x/sync v0.18.0 // indirect
|
||||||
golang.org/x/sys v0.37.0 // indirect
|
golang.org/x/sys v0.37.0 // indirect
|
||||||
golang.org/x/text v0.30.0 // indirect
|
golang.org/x/text v0.30.0 // indirect
|
||||||
golang.org/x/time v0.11.0 // indirect
|
golang.org/x/time v0.14.0 // indirect
|
||||||
golang.org/x/tools v0.38.0 // indirect
|
golang.org/x/tools v0.38.0 // indirect
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b // indirect
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101 // indirect
|
||||||
|
google.golang.org/grpc v1.76.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
gorm.io/driver/mysql v1.5.6 // indirect
|
gorm.io/driver/mysql v1.5.6 // indirect
|
||||||
gorm.io/driver/sqlite v1.6.0 // indirect
|
gorm.io/driver/sqlite v1.6.0 // indirect
|
||||||
|
|||||||
85
go.sum
85
go.sum
@@ -1,17 +1,38 @@
|
|||||||
|
cloud.google.com/go v0.115.0 h1:CnFSK6Xo3lDYRoBKEcAtia6VSC837/ZkJuRduSFnr14=
|
||||||
|
cloud.google.com/go v0.115.0/go.mod h1:8jIM5vVgoAEoiVxQ/O4BFTfHqulPZgs/ufEzMcFMdWU=
|
||||||
|
cloud.google.com/go/ai v0.8.0 h1:rXUEz8Wp2OlrM8r1bfmpF2+VKqc1VJpafE3HgzRnD/w=
|
||||||
|
cloud.google.com/go/ai v0.8.0/go.mod h1:t3Dfk4cM61sytiggo2UyGsDVW3RF1qGZaUKDrZFyqkE=
|
||||||
|
cloud.google.com/go/auth v0.17.0 h1:74yCm7hCj2rUyyAocqnFzsAYXgJhrG26XCFimrc/Kz4=
|
||||||
|
cloud.google.com/go/auth v0.17.0/go.mod h1:6wv/t5/6rOPAX4fJiRjKkJCvswLwdet7G8+UGXt7nCQ=
|
||||||
|
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
|
||||||
|
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
|
||||||
|
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
|
||||||
|
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
|
||||||
|
cloud.google.com/go/longrunning v0.5.7 h1:WLbHekDbjK1fVFD3ibpFFVoyizlLRl73I7YKuAKilhU=
|
||||||
|
cloud.google.com/go/longrunning v0.5.7/go.mod h1:8GClkudohy1Fxm3owmBGid8W0pSgodEMwEAztp38Xng=
|
||||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||||
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
||||||
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
||||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
|
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
|
||||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
|
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
|
||||||
|
github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 h1:aQ3y1lwWyqYPiWZThqv1aFbZMiM9vblcSArJRf2Irls=
|
||||||
|
github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M=
|
||||||
|
github.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A=
|
||||||
|
github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw=
|
||||||
|
github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8=
|
||||||
|
github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU=
|
||||||
|
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||||
|
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||||
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||||
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
github.com/go-openapi/analysis v0.23.0 h1:aGday7OWupfMs+LbmLZG4k0MYXIANxcuBTYUC03zFCU=
|
github.com/go-openapi/analysis v0.23.0 h1:aGday7OWupfMs+LbmLZG4k0MYXIANxcuBTYUC03zFCU=
|
||||||
@@ -67,10 +88,20 @@ github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0kt
|
|||||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||||
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
|
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
|
||||||
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
|
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
|
||||||
|
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||||
|
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||||
|
github.com/google/generative-ai-go v0.20.1 h1:6dEIujpgN2V0PgLhr6c/M1ynRdc7ARtiIDPFzj45uNQ=
|
||||||
|
github.com/google/generative-ai-go v0.20.1/go.mod h1:TjOnZJmZKzarWbjUJgy+r3Ee7HGBRVLhOIgupnwR4Bg=
|
||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
|
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
|
||||||
|
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/googleapis/enterprise-certificate-proxy v0.3.7 h1:zrn2Ee/nWmHulBx5sAVrGgAa0f2/R35S4DJwfFaUPFQ=
|
||||||
|
github.com/googleapis/enterprise-certificate-proxy v0.3.7/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
|
||||||
|
github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo=
|
||||||
|
github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc=
|
||||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||||
@@ -107,6 +138,8 @@ github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+
|
|||||||
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
|
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
|
||||||
github.com/panjf2000/ants/v2 v2.11.3 h1:AfI0ngBoXJmYOpDh9m516vjqoUu2sLrIVgppI9TZVpg=
|
github.com/panjf2000/ants/v2 v2.11.3 h1:AfI0ngBoXJmYOpDh9m516vjqoUu2sLrIVgppI9TZVpg=
|
||||||
github.com/panjf2000/ants/v2 v2.11.3/go.mod h1:8u92CYMUc6gyvTIw8Ru7Mt7+/ESnJahz5EVtqfrilek=
|
github.com/panjf2000/ants/v2 v2.11.3/go.mod h1:8u92CYMUc6gyvTIw8Ru7Mt7+/ESnJahz5EVtqfrilek=
|
||||||
|
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
|
||||||
|
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||||
@@ -138,14 +171,22 @@ github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQ
|
|||||||
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||||
go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd80=
|
go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd80=
|
||||||
go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c=
|
go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c=
|
||||||
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
|
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||||
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
|
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||||
go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI=
|
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ=
|
||||||
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
|
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo=
|
||||||
go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw=
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
|
||||||
go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg=
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
|
||||||
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
|
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
|
||||||
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
|
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
|
||||||
|
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
|
||||||
|
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
|
||||||
|
go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
|
||||||
|
go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
|
||||||
|
go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
|
||||||
|
go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
|
||||||
|
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
|
||||||
|
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
|
||||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
|
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
|
||||||
@@ -160,21 +201,33 @@ golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
|
|||||||
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
|
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
|
||||||
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
|
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
|
||||||
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
|
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
|
||||||
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo=
|
||||||
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||||
|
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
||||||
|
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
|
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
|
||||||
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
|
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
|
||||||
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
|
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
|
||||||
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
|
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||||
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||||
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
|
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
|
||||||
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
|
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
|
||||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||||
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
|
google.golang.org/api v0.256.0 h1:u6Khm8+F9sxbCTYNoBHg6/Hwv0N/i+V94MvkOSor6oI=
|
||||||
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
google.golang.org/api v0.256.0/go.mod h1:KIgPhksXADEKJlnEoRa9qAII4rXcy40vfI8HRqcU964=
|
||||||
|
google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4=
|
||||||
|
google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s=
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b h1:ULiyYQ0FdsJhwwZUwbaXpZF5yUE3h+RA+gxvBu37ucc=
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:oDOGiMSXHL4sDTJvFvIB9nRQCGdLP1o/iVaqQK8zB+M=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101 h1:tRPGkdGHuewF4UisLzzHHr1spKw92qLM98nIzxbC0wY=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||||
|
google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
|
||||||
|
google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=
|
||||||
|
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
||||||
|
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
|||||||
@@ -261,6 +261,7 @@ func (a *API) setupRoutes() {
|
|||||||
feedGroup.GET("/recipes", a.recipeController.ListRecipes)
|
feedGroup.GET("/recipes", a.recipeController.ListRecipes)
|
||||||
feedGroup.POST("/recipes/generate-from-all-materials/:pig_type_id", a.recipeController.GenerateFromAllMaterials)
|
feedGroup.POST("/recipes/generate-from-all-materials/:pig_type_id", a.recipeController.GenerateFromAllMaterials)
|
||||||
feedGroup.POST("/recipes/generate-prioritized-stock/:pig_type_id", a.recipeController.GenerateRecipeWithPrioritizedStockRawMaterials)
|
feedGroup.POST("/recipes/generate-prioritized-stock/:pig_type_id", a.recipeController.GenerateRecipeWithPrioritizedStockRawMaterials)
|
||||||
|
feedGroup.GET("/recipes/:id/ai-diagnose", a.recipeController.AIDiagnoseRecipe)
|
||||||
}
|
}
|
||||||
logger.Debug("饲料管理相关接口注册成功 (需要认证和审计)")
|
logger.Debug("饲料管理相关接口注册成功 (需要认证和审计)")
|
||||||
|
|
||||||
|
|||||||
@@ -256,3 +256,48 @@ func (c *RecipeController) GenerateRecipeWithPrioritizedStockRawMaterials(ctx ec
|
|||||||
logger.Infof("%s: 配方生成成功, 新配方ID: %d", actionType, resp.ID)
|
logger.Infof("%s: 配方生成成功, 新配方ID: %d", actionType, resp.ID)
|
||||||
return controller.SendSuccessWithAudit(ctx, controller.CodeCreated, "配方生成成功", resp, actionType, "配方生成成功", resp)
|
return controller.SendSuccessWithAudit(ctx, controller.CodeCreated, "配方生成成功", resp, actionType, "配方生成成功", resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AIDiagnoseRecipe godoc
|
||||||
|
// @Summary AI点评配方
|
||||||
|
// @Description 使用AI对指定配方进行点评,并针对目标猪类型给出建议。
|
||||||
|
// @Tags 饲料管理-配方
|
||||||
|
// @Security BearerAuth
|
||||||
|
// @Produce json
|
||||||
|
// @Param id path int true "配方ID"
|
||||||
|
// @Param pig_type_id query int true "猪类型ID"
|
||||||
|
// @Success 200 {object} controller.Response{data=dto.ReviewRecipeResponse} "业务码为200代表AI点评成功"
|
||||||
|
// @Router /api/v1/feed/recipes/{id}/ai-diagnose [get]
|
||||||
|
func (c *RecipeController) AIDiagnoseRecipe(ctx echo.Context) error {
|
||||||
|
reqCtx, logger := logs.Trace(ctx.Request().Context(), c.ctx, "AIDiagnoseRecipe")
|
||||||
|
const actionType = "AI点评配方"
|
||||||
|
|
||||||
|
// 从路径参数中获取配方ID
|
||||||
|
recipeIDStr := ctx.Param("id")
|
||||||
|
recipeID, err := strconv.ParseUint(recipeIDStr, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("%s: 配方ID格式错误: %v, ID: %s", actionType, err, recipeIDStr)
|
||||||
|
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的配方ID格式", actionType, "配方ID格式错误", recipeIDStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从查询参数中获取猪类型ID
|
||||||
|
pigTypeIDStr := ctx.QueryParam("pig_type_id")
|
||||||
|
pigTypeID, err := strconv.ParseUint(pigTypeIDStr, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("%s: 猪类型ID格式错误: %v, ID: %s", actionType, err, pigTypeIDStr)
|
||||||
|
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的猪类型ID格式", actionType, "猪类型ID格式错误", pigTypeIDStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用应用服务进行AI点评
|
||||||
|
reviewResponse, err := c.recipeService.AIDiagnoseRecipe(reqCtx, uint32(recipeID), uint32(pigTypeID))
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("%s: 服务层AI点评失败: %v, RecipeID: %d, PigTypeID: %d", actionType, err, recipeID, pigTypeID)
|
||||||
|
if errors.Is(err, service.ErrRecipeNotFound) {
|
||||||
|
return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, err.Error(), actionType, "配方或猪类型不存在", map[string]uint32{"recipe_id": uint32(recipeID), "pig_type_id": uint32(pigTypeID)})
|
||||||
|
}
|
||||||
|
// 对于其他错误,统一返回内部服务器错误
|
||||||
|
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "AI点评失败: "+err.Error(), actionType, "服务层AI点评失败", map[string]uint32{"recipe_id": uint32(recipeID), "pig_type_id": uint32(pigTypeID)})
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Infof("%s: AI点评成功, RecipeID: %d, PigTypeID: %d", actionType, recipeID, pigTypeID)
|
||||||
|
return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "AI点评成功", reviewResponse, actionType, "AI点评成功", reviewResponse)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package dto
|
package dto
|
||||||
|
|
||||||
|
import "git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
||||||
|
|
||||||
// =============================================================================================================
|
// =============================================================================================================
|
||||||
// 营养种类 (Nutrient) 相关 DTO
|
// 营养种类 (Nutrient) 相关 DTO
|
||||||
// =============================================================================================================
|
// =============================================================================================================
|
||||||
@@ -335,3 +337,14 @@ type GenerateRecipeResponse struct {
|
|||||||
Name string `json:"name"` // 新生成的配方名称
|
Name string `json:"name"` // 新生成的配方名称
|
||||||
Description string `json:"description"` // 新生成的配方描述
|
Description string `json:"description"` // 新生成的配方描述
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReviewRecipeRequest 定义了点评配方的请求体
|
||||||
|
type ReviewRecipeRequest struct {
|
||||||
|
PigTypeID uint32 `json:"pig_type_id" binding:"required"` // 猪类型ID
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReviewRecipeResponse 定义了点评配方的响应体
|
||||||
|
type ReviewRecipeResponse struct {
|
||||||
|
ReviewMessage string `json:"review_message"` // 点评内容
|
||||||
|
AIModel models.AIModel `json:"ai_model"` // 使用的 AI 模型
|
||||||
|
}
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ type RecipeService interface {
|
|||||||
GenerateRecipeWithAllRawMaterials(ctx context.Context, pigTypeID uint32) (*models.Recipe, error)
|
GenerateRecipeWithAllRawMaterials(ctx context.Context, pigTypeID uint32) (*models.Recipe, error)
|
||||||
// GenerateRecipeWithPrioritizedStockRawMaterials 生成新配方,优先使用有库存的原料
|
// GenerateRecipeWithPrioritizedStockRawMaterials 生成新配方,优先使用有库存的原料
|
||||||
GenerateRecipeWithPrioritizedStockRawMaterials(ctx context.Context, pigTypeID uint32) (*models.Recipe, error)
|
GenerateRecipeWithPrioritizedStockRawMaterials(ctx context.Context, pigTypeID uint32) (*models.Recipe, error)
|
||||||
|
// AIDiagnoseRecipe 智能诊断配方, 返回智能诊断结果和使用的AI模型
|
||||||
|
AIDiagnoseRecipe(ctx context.Context, recipeID uint32, pigTypeID uint32) (*dto.ReviewRecipeResponse, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// recipeServiceImpl 是 RecipeService 接口的实现
|
// recipeServiceImpl 是 RecipeService 接口的实现
|
||||||
@@ -175,3 +177,18 @@ func (s *recipeServiceImpl) ListRecipes(ctx context.Context, req *dto.ListRecipe
|
|||||||
|
|
||||||
return dto.ConvertRecipeListToDTO(recipes, total, req.Page, req.PageSize), nil
|
return dto.ConvertRecipeListToDTO(recipes, total, req.Page, req.PageSize), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AIDiagnoseRecipe 实现智能诊断配方的方法
|
||||||
|
func (s *recipeServiceImpl) AIDiagnoseRecipe(ctx context.Context, recipeID uint32, pigTypeID uint32) (*dto.ReviewRecipeResponse, error) {
|
||||||
|
serviceCtx := logs.AddFuncName(ctx, s.ctx, "AIDiagnoseRecipe")
|
||||||
|
|
||||||
|
reviewMessage, aiModel, err := s.recipeSvc.AIDiagnoseRecipe(serviceCtx, recipeID, pigTypeID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("AI 诊断配方失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &dto.ReviewRecipeResponse{
|
||||||
|
ReviewMessage: reviewMessage,
|
||||||
|
AIModel: aiModel,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import (
|
|||||||
"git.huangwc.com/pig/pig-farm-controller/internal/domain/plan"
|
"git.huangwc.com/pig/pig-farm-controller/internal/domain/plan"
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/domain/recipe"
|
"git.huangwc.com/pig/pig-farm-controller/internal/domain/recipe"
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/domain/task"
|
"git.huangwc.com/pig/pig-farm-controller/internal/domain/task"
|
||||||
|
infra_ai "git.huangwc.com/pig/pig-farm-controller/internal/infra/ai"
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/config"
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/config"
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/database"
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/database"
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
|
||||||
@@ -34,6 +35,7 @@ type Infrastructure struct {
|
|||||||
storage database.Storage
|
storage database.Storage
|
||||||
repos *Repositories
|
repos *Repositories
|
||||||
lora *LoraComponents
|
lora *LoraComponents
|
||||||
|
ai infra_ai.AI
|
||||||
tokenGenerator token.Generator
|
tokenGenerator token.Generator
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,10 +55,17 @@ func initInfrastructure(ctx context.Context, cfg *config.Config) (*Infrastructur
|
|||||||
|
|
||||||
tokenGenerator := token.NewTokenGenerator([]byte(cfg.App.JWTSecret))
|
tokenGenerator := token.NewTokenGenerator([]byte(cfg.App.JWTSecret))
|
||||||
|
|
||||||
|
// 初始化 AI
|
||||||
|
ai, err := initAI(ctx, cfg.AI)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("初始化 AI 管理器失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
return &Infrastructure{
|
return &Infrastructure{
|
||||||
storage: storage,
|
storage: storage,
|
||||||
repos: repos,
|
repos: repos,
|
||||||
lora: lora,
|
lora: lora,
|
||||||
|
ai: ai,
|
||||||
tokenGenerator: tokenGenerator,
|
tokenGenerator: tokenGenerator,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@@ -238,6 +247,8 @@ func initDomainServices(ctx context.Context, cfg *config.Config, infra *Infrastr
|
|||||||
pigTypeService,
|
pigTypeService,
|
||||||
recipeCoreService,
|
recipeCoreService,
|
||||||
recipeGenerateManager,
|
recipeGenerateManager,
|
||||||
|
infra.repos.recipeRepo,
|
||||||
|
infra.ai,
|
||||||
)
|
)
|
||||||
|
|
||||||
return &DomainServices{
|
return &DomainServices{
|
||||||
@@ -508,3 +519,12 @@ func initStorage(ctx context.Context, cfg config.DatabaseConfig) (database.Stora
|
|||||||
logs.GetLogger(ctx).Info("数据库初始化完成。")
|
logs.GetLogger(ctx).Info("数据库初始化完成。")
|
||||||
return storage, nil
|
return storage, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func initAI(ctx context.Context, cfg config.AIConfig) (infra_ai.AI, error) {
|
||||||
|
switch cfg.Model {
|
||||||
|
case models.AI_MODEL_GEMINI:
|
||||||
|
return infra_ai.NewGeminiAI(ctx, cfg.Gemini)
|
||||||
|
default:
|
||||||
|
return infra_ai.NewNoneAI(ctx), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,8 +2,11 @@ package recipe
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/ai"
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository"
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository"
|
||||||
@@ -22,6 +25,8 @@ type Service interface {
|
|||||||
GenerateRecipeWithAllRawMaterials(ctx context.Context, pigTypeID uint32) (*models.Recipe, error)
|
GenerateRecipeWithAllRawMaterials(ctx context.Context, pigTypeID uint32) (*models.Recipe, error)
|
||||||
// GenerateRecipeWithPrioritizedStockRawMaterials 生成新配方,优先使用有库存的原料
|
// GenerateRecipeWithPrioritizedStockRawMaterials 生成新配方,优先使用有库存的原料
|
||||||
GenerateRecipeWithPrioritizedStockRawMaterials(ctx context.Context, pigTypeID uint32) (*models.Recipe, error)
|
GenerateRecipeWithPrioritizedStockRawMaterials(ctx context.Context, pigTypeID uint32) (*models.Recipe, error)
|
||||||
|
// AIDiagnoseRecipe 智能诊断配方, 返回智能诊断结果
|
||||||
|
AIDiagnoseRecipe(ctx context.Context, recipeID uint32, pigTypeID uint32) (string, models.AIModel, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// recipeServiceImpl 是 Service 的实现,通过组合各个子服务来实现
|
// recipeServiceImpl 是 Service 的实现,通过组合各个子服务来实现
|
||||||
@@ -34,6 +39,9 @@ type recipeServiceImpl struct {
|
|||||||
PigTypeService
|
PigTypeService
|
||||||
RecipeCoreService
|
RecipeCoreService
|
||||||
RecipeGenerateManager
|
RecipeGenerateManager
|
||||||
|
|
||||||
|
recipeRepo repository.RecipeRepository
|
||||||
|
ai ai.AI
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRecipeService 创建一个新的 Service 实例
|
// NewRecipeService 创建一个新的 Service 实例
|
||||||
@@ -46,6 +54,8 @@ func NewRecipeService(
|
|||||||
pigTypeService PigTypeService,
|
pigTypeService PigTypeService,
|
||||||
recipeCoreService RecipeCoreService,
|
recipeCoreService RecipeCoreService,
|
||||||
recipeGenerateManager RecipeGenerateManager,
|
recipeGenerateManager RecipeGenerateManager,
|
||||||
|
recipeRepo repository.RecipeRepository,
|
||||||
|
ai ai.AI,
|
||||||
) Service {
|
) Service {
|
||||||
return &recipeServiceImpl{
|
return &recipeServiceImpl{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
@@ -56,6 +66,8 @@ func NewRecipeService(
|
|||||||
PigTypeService: pigTypeService,
|
PigTypeService: pigTypeService,
|
||||||
RecipeCoreService: recipeCoreService,
|
RecipeCoreService: recipeCoreService,
|
||||||
RecipeGenerateManager: recipeGenerateManager,
|
RecipeGenerateManager: recipeGenerateManager,
|
||||||
|
recipeRepo: recipeRepo,
|
||||||
|
ai: ai,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,3 +237,113 @@ func (r *recipeServiceImpl) GenerateRecipeWithPrioritizedStockRawMaterials(ctx c
|
|||||||
// 7. 返回创建的配方
|
// 7. 返回创建的配方
|
||||||
return recipe, nil
|
return recipe, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AIDiagnoseRecipe 使用 AI 为指定食谱生成诊断。
|
||||||
|
func (s *recipeServiceImpl) AIDiagnoseRecipe(ctx context.Context, recipeID uint32, pigTypeID uint32) (string, models.AIModel, error) {
|
||||||
|
serviceCtx, logger := logs.Trace(ctx, context.Background(), "AIDiagnoseRecipe")
|
||||||
|
|
||||||
|
// 1. 根据 recipeID 获取配方详情
|
||||||
|
recipe, err := s.recipeRepo.GetRecipeByID(serviceCtx, recipeID)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("获取配方详情失败: %v", err)
|
||||||
|
return "", s.ai.AIModel(), fmt.Errorf("获取配方详情失败: %w", err)
|
||||||
|
}
|
||||||
|
if recipe == nil {
|
||||||
|
logger.Warnf("未找到配方,ID: %d", recipeID)
|
||||||
|
return "", s.ai.AIModel(), fmt.Errorf("未找到配方,ID: %d", recipeID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 获取目标猪只类型信息
|
||||||
|
pigType, err := s.GetPigTypeByID(serviceCtx, pigTypeID)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("获取猪只类型信息失败: %v", err)
|
||||||
|
return "", s.ai.AIModel(), fmt.Errorf("获取猪只类型信息失败: %w", err)
|
||||||
|
}
|
||||||
|
if pigType == nil {
|
||||||
|
logger.Warnf("未找到猪只类型,ID: %d", pigTypeID)
|
||||||
|
return "", s.ai.AIModel(), fmt.Errorf("未找到猪只类型,ID: %d", pigTypeID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 定义 AI 输入结构体
|
||||||
|
type ingredientNutrient struct {
|
||||||
|
NutrientName string `json:"nutrient_name"`
|
||||||
|
Value float32 `json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type recipeIngredient struct {
|
||||||
|
RawMaterialName string `json:"raw_material_name"`
|
||||||
|
Percentage float32 `json:"percentage"`
|
||||||
|
Nutrients []ingredientNutrient `json:"nutrients"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type aiDiagnosisInput struct {
|
||||||
|
RecipeName string `json:"recipe_name"`
|
||||||
|
TargetPigType struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
} `json:"target_pig_type"`
|
||||||
|
Ingredients []recipeIngredient `json:"ingredients"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 填充 AI 输入结构体
|
||||||
|
input := aiDiagnosisInput{
|
||||||
|
RecipeName: recipe.Name,
|
||||||
|
}
|
||||||
|
|
||||||
|
input.TargetPigType.Name = fmt.Sprintf("%s-%s", pigType.Breed.Name, pigType.AgeStage.Name)
|
||||||
|
|
||||||
|
for _, ingredient := range recipe.RecipeIngredients {
|
||||||
|
if ingredient.RawMaterial.ID == 0 {
|
||||||
|
logger.Warnf("配方成分中存在未加载的原料信息,RecipeIngredientID: %d", ingredient.ID)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ing := recipeIngredient{
|
||||||
|
RawMaterialName: ingredient.RawMaterial.Name,
|
||||||
|
Percentage: ingredient.Percentage,
|
||||||
|
}
|
||||||
|
for _, rmn := range ingredient.RawMaterial.RawMaterialNutrients {
|
||||||
|
if rmn.Nutrient.ID == 0 {
|
||||||
|
logger.Warnf("原料营养成分中存在未加载的营养素信息,RawMaterialNutrientID: %d", rmn.ID)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ing.Nutrients = append(ing.Nutrients, ingredientNutrient{
|
||||||
|
NutrientName: rmn.Nutrient.Name,
|
||||||
|
Value: rmn.Value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
input.Ingredients = append(input.Ingredients, ing)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 序列化为 JSON 字符串
|
||||||
|
jsonBytes, err := json.Marshal(input)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("序列化配方和猪只类型信息为 JSON 失败: %v", err)
|
||||||
|
return "", s.ai.AIModel(), fmt.Errorf("序列化数据失败: %w", err)
|
||||||
|
}
|
||||||
|
jsonString := string(jsonBytes)
|
||||||
|
|
||||||
|
// 6. 构建 AI Prompt
|
||||||
|
var promptBuilder strings.Builder
|
||||||
|
promptBuilder.WriteString(`
|
||||||
|
你是一个专业的动物营养师。请根据以下猪饲料配方数据,生成一份详细的、对养殖户友好的说明报告。
|
||||||
|
说明报告应包括以下部分:
|
||||||
|
1. 诊断猪只配方是否合理,如合理需要说明为什么合理, 如不合理需给出详细的改进建议。
|
||||||
|
2. 关键成分分析:分析主要原料和营养成分的作用
|
||||||
|
3. 使用建议:提供使用此配方的最佳实践和注意事项。
|
||||||
|
\n`)
|
||||||
|
promptBuilder.WriteString("```")
|
||||||
|
promptBuilder.WriteString(jsonString)
|
||||||
|
promptBuilder.WriteString("```")
|
||||||
|
prompt := promptBuilder.String()
|
||||||
|
|
||||||
|
logger.Debugf("生成的 AI 诊断 Prompt: \n%s", prompt)
|
||||||
|
|
||||||
|
// 7. 调用 AI Manager 进行诊断
|
||||||
|
diagnosisResult, err := s.ai.GenerateReview(serviceCtx, prompt)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("调用 AI Manager 诊断配方失败: %v", err)
|
||||||
|
return "", s.ai.AIModel(), fmt.Errorf("AI 诊断失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Infof("成功对配方 ID: %d (目标猪只类型 ID: %d) 进行 AI 诊断。", recipeID, pigTypeID)
|
||||||
|
return diagnosisResult, s.ai.AIModel(), nil
|
||||||
|
}
|
||||||
|
|||||||
19
internal/infra/ai/ai.go
Normal file
19
internal/infra/ai/ai.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package ai
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AI 定义了通用的 AI 管理接口。
|
||||||
|
// 它可以用于处理各种 AI 相关的任务,例如文本生成、内容审核等。
|
||||||
|
type AI interface {
|
||||||
|
// GenerateReview 根据提供的文本内容生成评论。
|
||||||
|
// prompt: 用于生成评论的输入文本。
|
||||||
|
// 返回生成的评论字符串和可能发生的错误。
|
||||||
|
GenerateReview(ctx context.Context, prompt string) (string, error)
|
||||||
|
|
||||||
|
// AIModel 返回当前使用的 AI 模型。
|
||||||
|
AIModel() models.AIModel
|
||||||
|
}
|
||||||
73
internal/infra/ai/gemini.go
Normal file
73
internal/infra/ai/gemini.go
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
package ai
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/config"
|
||||||
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
|
||||||
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
||||||
|
|
||||||
|
"github.com/google/generative-ai-go/genai"
|
||||||
|
"google.golang.org/api/option"
|
||||||
|
)
|
||||||
|
|
||||||
|
// geminiImpl 是 Gemini AI 服务的实现。
|
||||||
|
type geminiImpl struct {
|
||||||
|
client *genai.GenerativeModel
|
||||||
|
cfg config.Gemini
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGeminiAI 创建一个新的 geminiImpl 实例。
|
||||||
|
func NewGeminiAI(ctx context.Context, cfg config.Gemini) (AI, error) {
|
||||||
|
// 检查 API Key 是否存在
|
||||||
|
if cfg.APIKey == "" {
|
||||||
|
return nil, fmt.Errorf("Gemini API Key 未配置")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建 Gemini 客户端
|
||||||
|
genaiClient, err := genai.NewClient(ctx, option.WithAPIKey(cfg.APIKey))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("创建 Gemini 客户端失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &geminiImpl{
|
||||||
|
client: genaiClient.GenerativeModel(cfg.ModelName),
|
||||||
|
cfg: cfg,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateReview 根据提供的文本内容生成评论。
|
||||||
|
func (g *geminiImpl) GenerateReview(ctx context.Context, prompt string) (string, error) {
|
||||||
|
serviceCtx, logger := logs.Trace(ctx, context.Background(), "GenerateReview")
|
||||||
|
logger.Debugf("开始调用 Gemini 生成评论,prompt: %s", prompt)
|
||||||
|
|
||||||
|
timeoutCtx, cancel := context.WithTimeout(serviceCtx, time.Duration(g.cfg.Timeout)*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
resp, err := g.client.GenerateContent(timeoutCtx, genai.Text(prompt))
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("调用 Gemini API 失败: %v", err)
|
||||||
|
return "", fmt.Errorf("调用 Gemini API 失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp == nil || len(resp.Candidates) == 0 || len(resp.Candidates[0].Content.Parts) == 0 {
|
||||||
|
logger.Warn("Gemini API 返回空内容或无候选评论")
|
||||||
|
return "", fmt.Errorf("Gemini API 返回空内容或无候选评论")
|
||||||
|
}
|
||||||
|
|
||||||
|
var review string
|
||||||
|
for _, part := range resp.Candidates[0].Content.Parts {
|
||||||
|
if txt, ok := part.(genai.Text); ok {
|
||||||
|
review += string(txt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Debugf("成功从 Gemini 生成评论: %s", review)
|
||||||
|
return review, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *geminiImpl) AIModel() models.AIModel {
|
||||||
|
return models.AI_MODEL_GEMINI
|
||||||
|
}
|
||||||
31
internal/infra/ai/no_ai.go
Normal file
31
internal/infra/ai/no_ai.go
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
package ai
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
|
||||||
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
var NoneAIError = errors.New("当前没有配置AI, 暂不支持此功能")
|
||||||
|
|
||||||
|
type NoneAI struct {
|
||||||
|
ctx context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewNoneAI(ctx context.Context) AI {
|
||||||
|
return &NoneAI{
|
||||||
|
ctx: ctx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NoneAI) GenerateReview(ctx context.Context, prompt string) (string, error) {
|
||||||
|
logger := logs.TraceLogger(ctx, n.ctx, "GenerateReview")
|
||||||
|
logger.Warnf("当前没有配置AI, 无法处理AI请求, 消息: %s", prompt)
|
||||||
|
return "", NoneAIError
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NoneAI) AIModel() models.AIModel {
|
||||||
|
return models.AI_MODEL_NONE
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -50,6 +51,9 @@ type Config struct {
|
|||||||
|
|
||||||
// AlarmNotification 告警通知配置
|
// AlarmNotification 告警通知配置
|
||||||
AlarmNotification AlarmNotificationConfig `yaml:"alarm_notification"`
|
AlarmNotification AlarmNotificationConfig `yaml:"alarm_notification"`
|
||||||
|
|
||||||
|
// AI AI服务配置
|
||||||
|
AI AIConfig `yaml:"ai"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// AppConfig 代表应用基础配置
|
// AppConfig 代表应用基础配置
|
||||||
@@ -231,6 +235,19 @@ type AlarmNotificationConfig struct {
|
|||||||
NotificationIntervals NotificationIntervalsConfig `yaml:"notification_intervals"`
|
NotificationIntervals NotificationIntervalsConfig `yaml:"notification_intervals"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AIConfig AI 服务配置
|
||||||
|
type AIConfig struct {
|
||||||
|
Model models.AIModel `yaml:"model"`
|
||||||
|
Gemini Gemini `yaml:"gemini"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gemini 代表 Gemini AI 服务的配置
|
||||||
|
type Gemini struct {
|
||||||
|
APIKey string `yaml:"api_key"` // Gemini API Key
|
||||||
|
ModelName string `yaml:"model_name"` // Gemini 模型名称,例如 "gemini-pro"
|
||||||
|
Timeout int `yaml:"timeout"` // AI 请求超时时间 (秒)
|
||||||
|
}
|
||||||
|
|
||||||
// NewConfig 创建并返回一个新的配置实例
|
// NewConfig 创建并返回一个新的配置实例
|
||||||
func NewConfig() *Config {
|
func NewConfig() *Config {
|
||||||
// 默认值可以在这里设置,但我们优先使用配置文件中的值
|
// 默认值可以在这里设置,但我们优先使用配置文件中的值
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/config"
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/config"
|
||||||
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"go.uber.org/zap/zapcore"
|
"go.uber.org/zap/zapcore"
|
||||||
"gopkg.in/natefinch/lumberjack.v2"
|
"gopkg.in/natefinch/lumberjack.v2"
|
||||||
@@ -65,8 +66,8 @@ func NewLogger(cfg config.LogConfig) *Logger {
|
|||||||
|
|
||||||
// 5. 构建 Logger
|
// 5. 构建 Logger
|
||||||
// zap.AddCaller() 会记录调用日志的代码行
|
// zap.AddCaller() 会记录调用日志的代码行
|
||||||
// zap.AddCallerSkip(1) 可以向上跳一层调用栈,如果我们将 logger.Info 等方法再封装一层,这个选项会很有用
|
// zap.AddCallerSkip(2) 可以向上跳两层调用栈,因为我们的日志方法被封装了两层 (Logger.Info -> Logger.logWithTrace)
|
||||||
zapLogger := zap.New(core, zap.AddCaller(), zap.AddCallerSkip(1))
|
zapLogger := zap.New(core, zap.AddCaller(), zap.AddCallerSkip(2))
|
||||||
|
|
||||||
return &Logger{sl: zapLogger.Sugar()}
|
return &Logger{sl: zapLogger.Sugar()}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,13 @@ import (
|
|||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type AIModel string
|
||||||
|
|
||||||
|
const (
|
||||||
|
AI_MODEL_NONE AIModel = "None"
|
||||||
|
AI_MODEL_GEMINI AIModel = "Gemini"
|
||||||
|
)
|
||||||
|
|
||||||
// Model 用于代替gorm.Model, 使用uint32以节约空间
|
// Model 用于代替gorm.Model, 使用uint32以节约空间
|
||||||
type Model struct {
|
type Model struct {
|
||||||
ID uint32 `gorm:"primarykey"`
|
ID uint32 `gorm:"primarykey"`
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ design/archive/2025-11-05-provide-logger-with-mothed/task-webhook.md
|
|||||||
design/archive/2025-11-06-health-check-routing/index.md
|
design/archive/2025-11-06-health-check-routing/index.md
|
||||||
design/archive/2025-11-06-system-plan-continuously-triggered/index.md
|
design/archive/2025-11-06-system-plan-continuously-triggered/index.md
|
||||||
design/archive/2025-11-10-exceeding-threshold-alarm/index.md
|
design/archive/2025-11-10-exceeding-threshold-alarm/index.md
|
||||||
design/recipe-management/index.md
|
design/archive/2025-11-29-recipe-management/index.md
|
||||||
docs/docs.go
|
docs/docs.go
|
||||||
docs/swagger.json
|
docs/swagger.json
|
||||||
docs/swagger.yaml
|
docs/swagger.yaml
|
||||||
|
|||||||
Reference in New Issue
Block a user