第二代仪评指标联识别接口

This commit is contained in:
BBIT-Kai
2025-09-30 13:48:59 +08:00
parent d71518931c
commit 3fb43c09f3
14 changed files with 323 additions and 36 deletions
+5 -4
View File
@@ -485,7 +485,7 @@ def get_ticket_image_list(user_id):
"created_at": MyUtils.format_datetime(row[0]), "created_at": MyUtils.format_datetime(row[0]),
"file_name": row[1], "file_name": row[1],
"resolution": row[2], "resolution": row[2],
"size": round(row[3], 2), "size": round(row[3] / 1024, 2),
"name": row[4], "name": row[4],
"moisture_content": row[5], "moisture_content": row[5],
"cocoon_weight": row[6], "cocoon_weight": row[6],
@@ -493,7 +493,6 @@ def get_ticket_image_list(user_id):
"fresh_shell_weight": row[8], "fresh_shell_weight": row[8],
"sample_count": row[9], "sample_count": row[9],
"barcode": row[10], "barcode": row[10],
# "oss_url": f"http://{SERVER_PATH_OSS}:9000/image-ticket/{row[11]}",
"oss_url": get_temp_url("image-ticket", row[11]), "oss_url": get_temp_url("image-ticket", row[11]),
"net_weight_total": row[12], "net_weight_total": row[12],
"evaluator": row[13], "evaluator": row[13],
@@ -513,6 +512,7 @@ def insert_ticket_image(
moisture_content, moisture_content,
cocoon_weight, cocoon_weight,
defective_pupa_count, defective_pupa_count,
dead_pupa_count,
fresh_shell_weight, fresh_shell_weight,
sample_count, sample_count,
barcode, barcode,
@@ -527,11 +527,11 @@ def insert_ticket_image(
""" """
INSERT INTO ticket_images ( INSERT INTO ticket_images (
created_by, file_name, resolution, size, name, created_by, file_name, resolution, size, name,
moisture_content, cocoon_weight, defective_pupa_count, moisture_content, cocoon_weight, defective_pupa_count, dead_pupa_count,
fresh_shell_weight, sample_count, barcode, oss, fresh_shell_weight, sample_count, barcode, oss,
net_weight_total, evaluator, reviewer, created_at net_weight_total, evaluator, reviewer, created_at
) )
VALUES (%s, %s, %s, %s, %s, VALUES (%s, %s, %s, %s, %s, %s,
%s, %s, %s, %s, %s, %s, %s, %s, %s, %s,
%s, %s, %s, %s, %s, NOW()) %s, %s, %s, %s, %s, NOW())
RETURNING id RETURNING id
@@ -545,6 +545,7 @@ def insert_ticket_image(
moisture_content, moisture_content,
cocoon_weight, cocoon_weight,
defective_pupa_count, defective_pupa_count,
dead_pupa_count,
fresh_shell_weight, fresh_shell_weight,
sample_count, sample_count,
barcode, barcode,
+67
View File
@@ -0,0 +1,67 @@
import json
import re
from langchain.schema import HumanMessage
from config.llm import *
from llm.ticketLLM import decode_barcode
def get_ticket_response_v2(image_url: str, needBarcode: bool = False):
# 构建 prompt
prompt_text = f"""
你是一位专业的图像分析AI。你的任务是仔细分析提供的图片内容,并按JSON格式输出结果。
## JSON输出结构及字段说明:
# 蚕茧检测信息数据模型
该模型用于描述蚕茧检测信息,每条记录包含以下字段:
## 1. 下足茧重量 (`cocoon_weight`)
- **类型**:浮点数
- **描述**:下足茧的重量,单位为克(可带小数)
## 2. 非蛹粒数 (`defective_pupa_count`)
- **类型**:整数
- **描述**:不合格蛹的数量,即非好蛹的个数
## 3. 僵蛹粒数 (`dead_pupa_count`)
- **类型**:整数
- **描述**:僵蚕蛹的数量
## 4. 鲜壳量 (`fresh_shell_weight`)
- **类型**:浮点数
- **描述**:鲜壳重量,单位为克(可带小数)
## 5. 小样粒数 (`sample_count`)
- **类型**:整数
- **描述**:检测使用的小样数量,即用于检测的茧粒数
---
### 注意事项
- 所有字段都是必填的(required),在 JSON 实例中必须提供值
- 浮点数字段可以包含小数,整数字段只能是整数
- `evaluator` 和 `reviewer` 可以为空字符串,但字段必须存在
- 如果图片与上述内容无关,则字段值为默认值,例如0、0.0、""
最后,只输出严格的 JSON 格式,不要包含其他文字、markdown等内容。
"""
messages = [
HumanMessage(
content=[
{"type": "text", "text": prompt_text},
{"type": "image_url", "image_url": {"url": image_url}},
]
)
]
# 直接调用模型
llmRes = llmVision.invoke(messages).content
# 去掉 ```json 和 ``` 包裹
cleaned = re.sub(r"^```json\s*|\s*```$", "", llmRes.strip())
# 解析 JSON
jsonRes = json.loads(cleaned)
print("needBarcode:", needBarcode)
if needBarcode:
jsonRes["barcode"] = decode_barcode(image_url)
return jsonRes
+7
View File
@@ -0,0 +1,7 @@
from pydantic import BaseModel
class F8ImageRequestV2(BaseModel):
title: str
image: str
needBarcode: bool
+24
View File
@@ -5,6 +5,7 @@ from fastapi import APIRouter
from config.app import F8_SERVER_USER_ID from config.app import F8_SERVER_USER_ID
from models.BaseResponse import BaseResponse from models.BaseResponse import BaseResponse
from models.F8ImageRequest import F8ImageRequest from models.F8ImageRequest import F8ImageRequest
from models.F8ImageRequestV2 import F8ImageRequestV2
from service.vision import process_ticket_image from service.vision import process_ticket_image
from utils import MyUtils from utils import MyUtils
@@ -20,6 +21,29 @@ async def cocoonTicket(data: F8ImageRequest):
img_bytes = base64.b64decode(input_data) img_bytes = base64.b64decode(input_data)
json_data = await MyUtils.async_task( json_data = await MyUtils.async_task(
process_ticket_image, process_ticket_image,
1,
True,
img_bytes,
f"{data.title}.jpg",
data.title,
F8_SERVER_USER_ID,
)
return BaseResponse(data=json_data)
except Exception as e:
return BaseResponse(status=False, message=f"解析失败: {str(e)}", data=None)
@f8Router.post("/createTicketImageTaskV2")
async def cocoonTicket(data: F8ImageRequestV2):
input_data = data.image
if "," in input_data:
input_data = input_data.split(",")[1]
try:
img_bytes = base64.b64decode(input_data)
json_data = await MyUtils.async_task(
process_ticket_image,
2,
data.needBarcode,
img_bytes, img_bytes,
f"{data.title}.jpg", f"{data.title}.jpg",
data.title, data.title,
+1 -1
View File
@@ -36,7 +36,7 @@ async def createTicketImageTask(
try: try:
contents = await file.read() contents = await file.read()
json_data = await MyUtils.async_task( json_data = await MyUtils.async_task(
process_ticket_image, contents, file.filename, projectName, user_id process_ticket_image, 1, True, contents, file.filename, projectName, user_id
) )
return BaseResponse(data=json_data) return BaseResponse(data=json_data)
except Exception as e: except Exception as e:
+14 -7
View File
@@ -5,10 +5,13 @@ import config.minIO as minIO
import db.postgres as pg import db.postgres as pg
from config.minIO import minio_client from config.minIO import minio_client
from llm.ticketLLM import * from llm.ticketLLM import *
from llm.ticketLLMv2 import get_ticket_response_v2
def process_ticket_image( def process_ticket_image(
img_bytes: bytes, version: int,
needBarcode: bool = False,
img_bytes=None,
file_name: str = None, file_name: str = None,
project_name: str = None, project_name: str = None,
user_id: UUID = None, user_id: UUID = None,
@@ -18,6 +21,8 @@ def process_ticket_image(
""" """
# 上传到 OSS,使用 UUID 做对象名 # 上传到 OSS,使用 UUID 做对象名
if img_bytes is None:
img_bytes = []
object_name = str(uuid.uuid4()) object_name = str(uuid.uuid4())
file_bytes = BytesIO(img_bytes) file_bytes = BytesIO(img_bytes)
bucket_name = "image-ticket" bucket_name = "image-ticket"
@@ -28,15 +33,16 @@ def process_ticket_image(
oss_url = minIO.get_temp_url(bucket_name, object_name) oss_url = minIO.get_temp_url(bucket_name, object_name)
# 调用分析方法获取 JSON # 调用分析方法获取 JSON
if version == 1:
json_data = get_ticket_response(oss_url)
elif version == 2:
json_data = get_ticket_response_v2(oss_url, needBarcode)
else:
json_data = get_ticket_response(oss_url) json_data = get_ticket_response(oss_url)
# 解析条码
barcode = decode_barcode(BytesIO(img_bytes))
json_data["barcode"] = barcode
# 获取图片分辨率和大小 # 获取图片分辨率和大小
img = Image.open(BytesIO(img_bytes)) img = Image.open(BytesIO(img_bytes))
resolution = f"{img.width}x{img.height}" resolution = f"{img.width}x{img.height}"
size_kb = len(img_bytes) / 1024 size_kb = round(len(img_bytes) / 1024, 2)
# 插入数据库 # 插入数据库
pg.insert_ticket_image( pg.insert_ticket_image(
@@ -45,12 +51,13 @@ def process_ticket_image(
resolution=resolution, resolution=resolution,
size=size_kb, size=size_kb,
name=project_name if project_name else object_name[:8], name=project_name if project_name else object_name[:8],
dead_pupa_count=json_data.get("moisture_content") if version == 2 else 0,
moisture_content=json_data.get("moisture_content"), moisture_content=json_data.get("moisture_content"),
cocoon_weight=json_data.get("cocoon_weight"), cocoon_weight=json_data.get("cocoon_weight"),
defective_pupa_count=json_data.get("defective_pupa_count"), defective_pupa_count=json_data.get("defective_pupa_count"),
fresh_shell_weight=json_data.get("fresh_shell_weight"), fresh_shell_weight=json_data.get("fresh_shell_weight"),
sample_count=json_data.get("sample_count"), sample_count=json_data.get("sample_count"),
barcode=barcode, barcode=json_data.get("barcode"),
oss=object_name, oss=object_name,
net_weight_total=json_data.get("net_weight_total"), net_weight_total=json_data.get("net_weight_total"),
evaluator=json_data.get("evaluator"), evaluator=json_data.get("evaluator"),
+4 -1
View File
@@ -100,7 +100,10 @@ dependencies {
implementation("io.ktor:ktor-client-serialization:$ktor_version") implementation("io.ktor:ktor-client-serialization:$ktor_version")
// 数据库迁移 // 数据库迁移
// implementation("org.flywaydb:flyway-core:10.13.0") // implementation("org.flywaydb:flyway-core:10.13.0")
// Kong AI
// implementation("ai.koog:koog-agents:0.3.0") // implementation("ai.koog:koog-agents:0.3.0")
// MinIO OSS
implementation("io.minio:minio:8.5.17") implementation("io.minio:minio:8.5.17")
// RocketMQ
implementation("org.apache.rocketmq:rocketmq-client-java:5.0.8")
} }
+40 -20
View File
@@ -41,27 +41,47 @@
公网域名:s1.ronsunny.cn 公网域名:s1.ronsunny.cn
| 公网端口 | 内网端口 | 框架 | docker服务/Host | docker端口 | 功能作用 | | 公网端口 | 内网端口 | docker端口 | docker服务/Host | 框架 | 功能作用 |
| -------- | -------- | ---------- | --------------- | ---------- | --------------------------------------- | | -------- | -------- | ---------- | ---------------- | ---------- | ------------------------------------------------------------ |
| | | Vue | ce_vue | 8090 | vue前端服务 | | | | 8090 | ce_vue | Vue | vue前端服务 |
| | | FastAPI | ce_pybackend | 13011 | python后端程序 | | | 8089 | | | Ktor | 实验室业务后端 |
| | 5432 | PostgreSQL | ce_postgres | 5432 | PostgreSQL数据库 | | | | 13011 | ce_pybackend | FastAPI | 实验室AI后端 |
| | 6379 | Redis | ce_redis | 6379 | Radis数据库 | | | 5432 | 5432 | ce_postgres | PostgreSQL | PostgreSQL数据库 |
| | | Etcd | ce_etcd | 2379 | EtcdKey-Value 存储,给ce_milvus使用 | | | 6379 | 6379 | ce_redis | Redis | Radis数据库 |
| **9000** | 9000 | MinIO | ce_minio | 9000 | MinIO数据访问 | | | | 2379 | ce_etcd | Etcd | EtcdKey-Value 存储,给ce_milvus使用 |
| | 9001 | | ce_minio | 9001 | MinIOWeb控制端 | | **9000** | 9000 | 9000 | ce_minio | MinIO | MinIO数据访问 |
| | 19530 | Milvus | ce_milvus | 19530 | Minvus数据访问 | | | 9001 | 9001 | ce_minio | MinIO | MinIOWeb控制端 |
| | 9091 | | ce_milvus | 9091 | MinvusWeb控制端,需要加/webui | | | 19530 | 19530 | ce_milvus | Milvus | Minvus数据访问 |
| | 3000 | Attu | ce_attu | 3000 | AttuMinvus的**可视化**控制 | | | 9091 | 9091 | ce_milvus | Milvus | MinvusWeb控制端,需要加/webui |
| **8090** | 8090 | Kong | ce_kong | 8090 | Kong网关 | | | 3000 | 3000 | ce_attu | Attu | AttuMinvus的**可视化**控制 |
| | 8001 | | ce_kong | 8001 | Kong Admin API | | **8090** | 8090 | 8090 | ce_kong | Kong | Kong网关 |
| | 8002 | | ce_kong | 8002 | Kong Admin API 的 HTTPS 端口 | | | 8001 | 8001 | ce_kong | Kong | Kong Admin API |
| | 8444 | | ce_kong | 8444 | Kong **可视化**管理界面 | | | 8002 | 8002 | ce_kong | Kong | Kong **可视化**管理界面 |
| | 8088 | ws-scrcpy | | | Android远程框架 | | | 8444 | 8444 | ce_kong | Kong | Kong Admin API 的 HTTPS 端口 |
| 8088 | | | | | 已废弃,原Android远程框架,已由网关控制 | | | 8088 | | | ws-scrcpy | Android远程框架 |
| 8089 | | | | | 已废弃,原Ktor后端服务,已由网关控制 | | | 9876 | 9876 | ce_rmq_name_srv | RocketMQ | NameServer |
| 13011 | | | | | 已废弃:原FastAPI后端服务,已由网关控制 | | | 8080 | 8080 | ce_rmq_proxy | RocketMQ | RocketMQ Proxy 的 **消息发送/消费 REST API 或 TCP 代理端口** |
| | 8081 | 8081 | ce_rmq_proxy | RocketMQ | RocketMQ Proxy 的 **管理/监控接口** |
| | 8082 | 8082 | ce_rmq_dashboard | RocketMQ | RocketMQ Dashboard **Web UI 端口** |
| | 10909 | 10909 | ce_rmq_broker_a | RocketMQ | broker 内部默认端口 |
| | 10911 | 10911 | ce_rmq_broker_a | RocketMQ | Consumer 拉取消息的端口 |
| | 10912 | 10912 | ce_rmq_broker_a | RocketMQ | Broker 之间的集群复制/同步 |
| | 10929 | 10909 | ce_rmq_broker_b | RocketMQ | broker 内部默认端口 |
| | 10931 | 10911 | ce_rmq_broker_b | RocketMQ | Consumer 拉取消息的端口 |
| | 10932 | 10912 | ce_rmq_broker_b | RocketMQ | Broker 之间的集群复制/同步 |
| 8088 | | | | | 建议后续关闭,原Android远程框架,已由网关控制 |
| 8089 | | | | | 建议后续关闭,原Ktor后端服务,已由网关控制 |
| 13011 | | | | | 已空闲:原FastAPI后端服务,已由网关控制 |
| 服务 | 端口 | 用途 |
| ---------- | ----- | ----------------------------------- |
| NameServer | 9876 | 注册/查询 Broker 地址,服务发现 |
| Broker | 10909 | 内部集群通信(心跳、同步) |
| | 10911 | 外部客户端通信(Producer/Consumer |
| | 10912 | HA 主从同步(可选) |
| Proxy | 8080 | HTTP/REST 客户端访问 Broker |
| | 8081 | Proxy 管理/状态监控 |
| Dashboard | 8082 | Web UI 可视化监控 |
## 三、部署 ## 三、部署
@@ -192,6 +192,77 @@ services:
- ce_network - ce_network
restart: unless-stopped restart: unless-stopped
# ---------- RocketMQ ----------
namesrv:
image: apache/rocketmq:5.3.3
container_name: ce_rmq_name_srv
ports:
- 9876:9876
networks:
- ce_network
command: sh mqnamesrv
broker1:
image: apache/rocketmq:5.3.3
container_name: ce_rmq_broker_a
ports:
- 10909:10909
- 10911:10911
- 10912:10912
environment:
- NAMESRV_ADDR=ce_rmq_name_srv:9876
depends_on:
- namesrv
networks:
- ce_network
volumes:
- ./rocketmq_config/broker1.conf:/opt/broker.conf
command: sh mqbroker -c /opt/broker.conf
broker2:
image: apache/rocketmq:5.3.3
container_name: ce_rmq_broker_b
links:
- namesrv
ports:
- 10929:10909
- 10931:10911
- 10932:10912
environment:
- NAMESRV_ADDR=ce_rmq_name_srv:9876 # NameServer 地址
volumes:
- ./rocketmq_config/broker2.conf:/opt/broker.conf
command: sh mqbroker -c /opt/broker.conf
networks:
- ce_network
proxy:
image: apache/rocketmq:5.3.3
container_name: ce_rmq_proxy
networks:
- ce_network
depends_on:
- broker1
- broker2
- namesrv
ports:
- 8080:8080
- 8081:8081
restart: on-failure
environment:
- NAMESRV_ADDR=ce_rmq_broker:9876
command: sh mqproxy
rocketmq-dashboard:
image: apacherocketmq/rocketmq-dashboard:2.1.0
container_name: ce_rmq_dashboard
environment:
- JAVA_OPTS=-Drocketmq.namesrv.addr=ce_rmq_name_srv:9876
ports:
- "8082:8082"
restart: unless-stopped
depends_on:
- broker1
- broker2
- namesrv
networks:
- ce_network
# ---------- 数据卷 ---------- # ---------- 数据卷 ----------
volumes: volumes:
postgres_data: postgres_data:
@@ -207,7 +278,6 @@ volumes:
driver_opts: driver_opts:
type: tmpfs type: tmpfs
device: tmpfs device: tmpfs
# ---------- 网络 ---------- # ---------- 网络 ----------
networks: networks:
ce_network: ce_network:
@@ -0,0 +1,9 @@
brokerClusterName = AICluster
brokerName = broker-a
brokerId = 0
deleteWhen = 04
fileReservedTime = 48
brokerRole = ASYNC_MASTER
flushDiskType = ASYNC_FLUSH
storePathRootDir=/home/rocketmq/store
storePathCommitLog=/home/rocketmq/store/commitlog
@@ -0,0 +1,9 @@
brokerClusterName = AICluster
brokerName = broker-b
brokerId = 1
deleteWhen = 04
fileReservedTime = 48
brokerRole = SLAVE
flushDiskType = ASYNC_FLUSH
storePathRootDir=/home/rocketmq/store
storePathCommitLog=/home/rocketmq/store/commitlog
@@ -0,0 +1,3 @@
{
"rocketMQClusterName": "AICluster"
}
@@ -7,7 +7,7 @@ const routes: RouteRecordRaw[] = [
{ {
meta: { meta: {
icon: 'ic:round-remove-red-eye', icon: 'ic:round-remove-red-eye',
authority: ['iva', 'sca', 'ysa'], authority: ['iva', 'sca', 'ysa', 'ticket'],
keepAlive: true, keepAlive: true,
order: 2, order: 2,
title: $t('计算机视觉'), title: $t('计算机视觉'),
@@ -0,0 +1,67 @@
import type { RouteRecordRaw } from 'vue-router';
import { $t } from '#/locales';
const IFrameView = () => import('@vben/layouts').then((m) => m.IFrameView);
const routes: RouteRecordRaw[] = [
{
meta: {
icon: 'ic:round-handyman',
keepAlive: false,
authority: ['set'],
order: 2,
title: $t('控制后台'),
},
name: 'Set',
path: '/set',
children: [
{
name: 'Kong-set',
path: '/set/kong',
component: IFrameView,
meta: {
icon: 'mdi:server-network',
link: 'http://10.10.10.9:8002/',
keepAlive: true,
title: '网关控制后台',
},
},
{
name: 'Milvus-set',
path: '/set/kong',
component: IFrameView,
meta: {
icon: 'mdi:database',
link: 'http://10.10.10.9:3000/',
keepAlive: true,
title: 'Milvus Attu后台',
},
},
{
name: 'Milvus-webui',
path: '/set/kong',
component: IFrameView,
meta: {
icon: 'mdi:monitor-dashboard',
link: 'http://10.10.10.9:9091/webui',
keepAlive: true,
title: 'Milvus 自带后台',
},
},
{
name: 'RocketMQ-webui',
path: '/set/rocketMQ',
component: IFrameView,
meta: {
icon: 'mdi:server-network',
link: 'http://10.10.10.9:8082',
keepAlive: true,
title: 'RocketMQ可视化后台',
},
},
],
},
];
export default routes;