feat: 集成 AI-Core gRPC 文档解析服务

- 新增 AICoreClient 客户端
- 添加 document_parser_client.go
- 知识库服务集成 AI-Core 解析
- 配置中添加 ai_core_service_addr

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-09 12:50:27 +08:00
parent 0d4fd6b425
commit dc1c825d2e
8 changed files with 227 additions and 17 deletions

View File

@@ -0,0 +1,132 @@
package service
import (
"context"
"fmt"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
// AICoreClient AI-Core 文档解析服务客户端
type AICoreClient struct {
conn *grpc.ClientConn
address string
}
// ParseResult 解析结果
type ParseResult struct {
Success bool
Content string
Message string
ContentLength int32
FileType string
ParserEngine string
}
// NewAICoreClient 创建 AI-Core 客户端
func NewAICoreClient(address string) (*AICoreClient, error) {
return &AICoreClient{address: address}, nil
}
// Connect 连接到 gRPC 服务
func (c *AICoreClient) Connect() error {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
conn, err := grpc.DialContext(ctx, c.address,
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithBlock(),
)
if err != nil {
return fmt.Errorf("failed to connect to AI-Core service: %w", err)
}
c.conn = conn
return nil
}
// Close 关闭连接
func (c *AICoreClient) Close() {
if c.conn != nil {
c.conn.Close()
}
}
// ParseDocument 解析文档
func (c *AICoreClient) ParseDocument(fileURL, fileName, fileType string) (*ParseResult, error) {
if c.conn == nil {
if err := c.Connect(); err != nil {
return nil, err
}
}
// 使用 gRPC raw bytes 调用
// 由于没有生成 protobuf 代码,使用 raw bytes 方式调用
client := NewDocumentParserClient(c.conn)
req := &ParseRequest{
FileUrl: fileURL,
FileName: fileName,
FileType: fileType,
}
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
resp, err := client.ParseDocument(ctx, req)
if err != nil {
return nil, fmt.Errorf("failed to parse document: %w", err)
}
return &ParseResult{
Success: resp.Success,
Content: resp.Content,
Message: resp.Message,
ContentLength: resp.ContentLength,
FileType: resp.FileType,
ParserEngine: resp.ParserEngine,
}, nil
}
// 以下是手动定义的 protobuf messages与 proto 文件一致)
// 不需要生成 .pb.go 文件,直接手动定义
type ParseRequest struct {
FileUrl string
FileName string
FileType string
ParserEngine string
}
type ParseResponse struct {
Success bool
Content string
Message string
ContentLength int32
FileType string
ParserEngine string
}
// DocumentParserClient gRPC 客户端接口(手动实现)
type DocumentParserClient interface {
ParseDocument(ctx context.Context, in *ParseRequest, opts ...grpc.CallOption) (*ParseResponse, error)
}
type documentParserClient struct {
cc grpc.ClientConnInterface
}
// NewDocumentParserClient 创建 DocumentParser 客户端
func NewDocumentParserClient(cc grpc.ClientConnInterface) DocumentParserClient {
return &documentParserClient{cc: cc}
}
func (c *documentParserClient) ParseDocument(ctx context.Context, in *ParseRequest, opts ...grpc.CallOption) (*ParseResponse, error) {
out := new(ParseResponse)
err := c.cc.Invoke(ctx, "/docparser.DocumentParser/ParseDocument", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}

View File

@@ -24,18 +24,21 @@ func init() {
}
type KnowledgeService struct {
repo *repository.KnowledgeRepository
modelRepo *repository.ModelRepository
uploadService *UploadService
repo *repository.KnowledgeRepository
modelRepo *repository.ModelRepository
uploadService *UploadService
pythonServiceURL string
aiCoreClient *AICoreClient
}
func NewKnowledgeService(repo *repository.KnowledgeRepository, modelRepo *repository.ModelRepository, uploadService *UploadService, pythonServiceURL string) *KnowledgeService {
func NewKnowledgeService(repo *repository.KnowledgeRepository, modelRepo *repository.ModelRepository, uploadService *UploadService, pythonServiceURL, aiCoreServiceAddr string) *KnowledgeService {
aiCoreClient, _ := NewAICoreClient(aiCoreServiceAddr)
return &KnowledgeService{
repo: repo,
modelRepo: modelRepo,
uploadService: uploadService,
repo: repo,
modelRepo: modelRepo,
uploadService: uploadService,
pythonServiceURL: pythonServiceURL,
aiCoreClient: aiCoreClient,
}
}
@@ -227,6 +230,9 @@ func (s *KnowledgeService) UploadDocument(kbID string, file *multipart.FileHeade
// 异步调用 Python 服务解析文档
go s.parseDocument(kbID, doc.ID, result.URL, kb.ParsingConfig)
// 异步调用 AI-Core gRPC 服务解析文档(获取 Markdown
go s.parseDocumentWithAICore(doc.ID, result.URL, doc.Name)
return doc, result.URL, nil
}
@@ -284,6 +290,32 @@ func (s *KnowledgeService) parseDocument(kbID, docID, fileURL string, config mod
}
}
// parseDocumentWithAICore 调用 AI-Core gRPC 服务解析文档
func (s *KnowledgeService) parseDocumentWithAICore(docID, fileURL, fileName string) {
if s.aiCoreClient == nil {
knowledgeDebugLog.Printf("[AICore] AI-Core 客户端未初始化")
return
}
knowledgeDebugLog.Printf("[AICore] 开始解析文档: docID=%s, fileURL=%s, fileName=%s", docID, fileURL, fileName)
result, err := s.aiCoreClient.ParseDocument(fileURL, fileName, "")
if err != nil {
knowledgeDebugLog.Printf("[AICore] 解析失败: docID=%s, err=%v", docID, err)
return
}
if result.Success && result.Content != "" {
knowledgeDebugLog.Printf("[AICore] 解析成功: docID=%s, contentLength=%d", docID, len(result.Content))
// 更新文档的 Content 字段
s.repo.UpdateDocument(docID, map[string]interface{}{
"content": result.Content,
})
} else {
knowledgeDebugLog.Printf("[AICore] 解析返回失败: docID=%s, message=%s", docID, result.Message)
}
}
// DeleteDocument 删除文档
func (s *KnowledgeService) DeleteDocument(kbID, docID string) error {
// 验证文档存在