4
This commit is contained in:
79
proto/README.md
Normal file
79
proto/README.md
Normal file
@@ -0,0 +1,79 @@
|
||||
# proto — 跨语言 API 契约
|
||||
|
||||
本目录是系统的**语言中立契约源**。`contract.proto` 定义了内核与插件之间、以及外部系统与本系统交互所需的全部 gRPC 服务与消息。任何语言都可以从这里生成客户端/服务端代码来接入本系统。
|
||||
|
||||
```
|
||||
proto/
|
||||
contract/v1/contract.proto 契约源(唯一可信来源)
|
||||
```
|
||||
|
||||
> Go 生成代码位于 `src/go/proto/contract/v1/`(已入库,克隆即可 `go build`)。其它语言的生成代码不入库,由各使用方按需生成。
|
||||
|
||||
## 契约内容
|
||||
|
||||
`contract.v1` package 定义:
|
||||
|
||||
**通用服务(每个插件都实现)**
|
||||
- `PluginManifest` — `GetManifest`、`HealthCheck`
|
||||
- `InvocationBroker` — `Invoke`(内核托管,插件互调入口)、`ListPlugins`
|
||||
|
||||
**枚举与基础消息**
|
||||
- `PluginTier` — `BASE` / `MID` / `COMPLEX`
|
||||
- `Dependency`、`Capability`、`PluginInfo`、`TrafficEntry`、`UsageInfo`
|
||||
|
||||
**业务服务(由插件实现)**
|
||||
- `EchoService`、`TransformService`、`StreamingDemo` — 样例(验证三层与流式)
|
||||
- `RecorderService` — 留存与查询(Save/List/ReadBlob/Stats)
|
||||
- `UsageService` — 用量解析(ParseBody/ParseStream)
|
||||
- `ProxyService` — 外部流量处理入口(Handle)
|
||||
- `DashboardService` — 只读管理界面渲染(Render)
|
||||
|
||||
## 跨语言生成示例
|
||||
|
||||
先确保安装了 protoc 及对应语言插件,然后从仓库根目录执行(`--proto_path` 指向本目录)。
|
||||
|
||||
**Python**
|
||||
```bash
|
||||
python -m grpc_tools.protoc \
|
||||
--proto_path=proto \
|
||||
--python_out=out/python --grpc_python_out=out/python \
|
||||
contract/v1/contract.proto
|
||||
```
|
||||
|
||||
**TypeScript / JavaScript(ts-proto)**
|
||||
```bash
|
||||
protoc \
|
||||
--proto_path=proto \
|
||||
--plugin=protoc-gen-ts_proto=./node_modules/.bin/protoc-gen-ts_proto \
|
||||
--ts_proto_out=out/ts \
|
||||
contract/v1/contract.proto
|
||||
```
|
||||
|
||||
**Java**
|
||||
```bash
|
||||
protoc \
|
||||
--proto_path=proto \
|
||||
--java_out=out/java --grpc-java_out=out/java \
|
||||
contract/v1/contract.proto
|
||||
```
|
||||
|
||||
**Go**(已配置在 `scripts/genproto.ps1`,无需手动)
|
||||
```powershell
|
||||
.\scripts\genproto.ps1 # 从 proto/ 生成到 src/go/proto/
|
||||
```
|
||||
|
||||
## 互调约定
|
||||
|
||||
插件间调用全部经内核 `InvocationBroker.Invoke`:
|
||||
- `target_plugin` — 目标插件名(如 `"echo-base"`)
|
||||
- `method` — gRPC 方法全路径(如 `"/contract.v1.EchoService/Echo"`)
|
||||
- `payload` — 目标请求消息的 proto-wire 字节(用本契约生成的类型序列化)
|
||||
|
||||
返回的 `result` 是目标响应消息的 proto-wire 字节,用同一契约反序列化即可。内核不感知具体类型,只透传字节——这正是契约必须语言中立、独立存在的原因。
|
||||
|
||||
## 修改契约
|
||||
|
||||
改 `contract.proto` 后:
|
||||
1. 重新生成 Go 代码:`.\scripts\genproto.ps1`
|
||||
2. 提交更新后的 `proto/contract/v1/contract.proto` 与 `src/go/proto/contract/v1/*.pb.go`
|
||||
3. 通知各语言使用方按上面的命令重新生成
|
||||
181
proto/contract/v1/contract.proto
Normal file
181
proto/contract/v1/contract.proto
Normal file
@@ -0,0 +1,181 @@
|
||||
syntax = "proto3";
|
||||
package contract.v1;
|
||||
option go_package = "goaiapi/proto/contract/v1;contractv1";
|
||||
|
||||
// ─── 通用服务:每个插件都必须实现 ───
|
||||
service PluginManifest {
|
||||
rpc GetManifest(GetManifestRequest) returns (GetManifestResponse);
|
||||
rpc HealthCheck(HealthCheckRequest) returns (HealthCheckResponse);
|
||||
}
|
||||
|
||||
message GetManifestRequest {}
|
||||
message GetManifestResponse {
|
||||
string name = 1; // 插件唯一名,如 "echo-base"
|
||||
string version = 2; // semver,如 "1.0.0"
|
||||
PluginTier tier = 3; // 声明的层级(内核会用 DAG 复算校验)
|
||||
repeated Dependency deps = 4;
|
||||
repeated Capability caps = 5; // 本插件提供的 gRPC service 名
|
||||
}
|
||||
|
||||
enum PluginTier {
|
||||
TIER_UNSPECIFIED = 0;
|
||||
TIER_BASE = 1; // 无插件依赖
|
||||
TIER_MID = 2; // 仅依赖 base
|
||||
TIER_COMPLEX = 3; // 依赖至少一个非 base
|
||||
}
|
||||
|
||||
message Dependency {
|
||||
string name = 1; // 依赖的插件名
|
||||
string version_constraint = 2; // semver 约束,如 ">=1.0.0 <2.0.0"
|
||||
}
|
||||
|
||||
message Capability {
|
||||
string service_name = 1; // 如 "EchoService"
|
||||
}
|
||||
|
||||
message HealthCheckRequest {}
|
||||
message HealthCheckResponse {
|
||||
bool healthy = 1;
|
||||
string message = 2;
|
||||
}
|
||||
|
||||
// ─── 调用代理:由内核托管,被插件调用 ───
|
||||
service InvocationBroker {
|
||||
rpc Invoke(InvokeRequest) returns (InvokeResponse);
|
||||
rpc ListPlugins(ListPluginsRequest) returns (ListPluginsResponse);
|
||||
}
|
||||
|
||||
message InvokeRequest {
|
||||
string target_plugin = 1; // 目标插件名
|
||||
string method = 2; // 目标方法全路径,如 "/contract.v1.EchoService/Echo"
|
||||
bytes payload = 3; // 已序列化的请求(proto wire 字节)
|
||||
}
|
||||
message InvokeResponse {
|
||||
bytes result = 1; // 已序列化的响应
|
||||
string error = 2;
|
||||
}
|
||||
|
||||
message ListPluginsRequest {}
|
||||
message ListPluginsResponse { repeated PluginInfo plugins = 1; }
|
||||
message PluginInfo {
|
||||
string name = 1;
|
||||
string version = 2;
|
||||
PluginTier tier = 3;
|
||||
bool healthy = 4;
|
||||
}
|
||||
|
||||
// ─── 样板服务 ───
|
||||
|
||||
// 基本插件
|
||||
service EchoService { rpc Echo(EchoRequest) returns (EchoResponse); }
|
||||
message EchoRequest { string message = 1; }
|
||||
message EchoResponse { string message = 1; string echoed_by = 2; }
|
||||
|
||||
// 依赖基本(内部调 Echo)
|
||||
service TransformService { rpc Transform(TransformRequest) returns (TransformResponse); }
|
||||
message TransformRequest { string text = 1; string operation = 2; } // upper|lower|reverse
|
||||
message TransformResponse { string result = 1; string via_echo = 2; }
|
||||
|
||||
// 复杂(依赖 base+mid,含 server-streaming,验证 SSE-over-gRPC)
|
||||
service StreamingDemo {
|
||||
rpc UnaryPing(PingRequest) returns (PingResponse);
|
||||
rpc StreamEvents(StreamRequest) returns (stream StreamEvent);
|
||||
}
|
||||
message PingRequest { string message = 1; }
|
||||
message PingResponse { string message = 1; string pipeline = 2; }
|
||||
message StreamRequest { int32 count = 1; int32 interval_ms = 2; }
|
||||
message StreamEvent { int32 sequence = 1; string payload = 2; int64 timestamp_ns = 3; }
|
||||
|
||||
// ─── Relay 业务服务(核心能力下沉为插件) ───
|
||||
|
||||
// 一条请求-响应留存记录的可序列化表示(跨插件传递用)。
|
||||
message TrafficEntry {
|
||||
int64 id = 1;
|
||||
int64 timestamp_ms = 2;
|
||||
string client_ip = 3;
|
||||
string method = 4;
|
||||
string path = 5;
|
||||
map<string,string> req_header = 6;
|
||||
bytes req_body = 7;
|
||||
int32 status = 8;
|
||||
map<string,string> resp_header = 9;
|
||||
bytes resp_body = 10;
|
||||
int64 duration_ms = 11;
|
||||
string api_format = 12; // openai | claude | ollama | unknown
|
||||
string model = 13;
|
||||
int32 input_tokens = 14;
|
||||
int32 output_tokens = 15;
|
||||
int32 total_tokens = 16;
|
||||
bool is_stream = 17;
|
||||
string blob_path = 18;
|
||||
}
|
||||
|
||||
// UsageInfo:从响应体解析出的用量信息。
|
||||
message UsageInfo {
|
||||
string api_format = 1;
|
||||
string model = 2;
|
||||
int32 input_tokens = 3;
|
||||
int32 output_tokens = 4;
|
||||
int32 total_tokens = 5;
|
||||
}
|
||||
|
||||
// ── RecorderService(BASE):留存 + 查询 ──
|
||||
service RecorderService {
|
||||
rpc Save(SaveRequest) returns (SaveResponse);
|
||||
rpc List(ListRequest) returns (ListResponse);
|
||||
rpc ReadBlob(ReadBlobRequest) returns (ReadBlobResponse);
|
||||
rpc Stats(StatsRequest) returns (StatsResponse);
|
||||
}
|
||||
message SaveRequest { TrafficEntry entry = 1; }
|
||||
message SaveResponse { int64 id = 1; string error = 2; }
|
||||
message ListRequest { int32 limit = 1; int32 offset = 2; string model = 3; string format = 4; int32 status = 5; }
|
||||
message ListResponse { repeated TrafficEntry records = 1; int32 total = 2; }
|
||||
message ReadBlobRequest { int64 id = 1; }
|
||||
message ReadBlobResponse { TrafficEntry entry = 1; string error = 2; }
|
||||
message StatsRequest {}
|
||||
message ModelStat { string model = 1; int32 count = 2; int64 input_tokens = 3; int64 output_tokens = 4; int64 total_tokens = 5; }
|
||||
message StatsResponse {
|
||||
int32 total_requests = 1;
|
||||
int32 success_count = 2;
|
||||
int32 error_count = 3;
|
||||
int64 total_tokens = 4;
|
||||
repeated ModelStat by_model = 5;
|
||||
}
|
||||
|
||||
// ── UsageService(BASE):用量解析(非流式整体 / 流式全文) ──
|
||||
service UsageService {
|
||||
rpc ParseBody(ParseBodyRequest) returns (UsageInfo);
|
||||
rpc ParseStream(ParseStreamRequest) returns (UsageInfo);
|
||||
}
|
||||
message ParseBodyRequest { bytes body = 1; string path = 2; }
|
||||
message ParseStreamRequest { bytes raw = 1; string path = 2; } // raw = 拼回的全量 SSE 原文
|
||||
|
||||
// ── ProxyService(COMPLEX):内核把外部请求转发到此,由它转上游并触发留存 ──
|
||||
service ProxyService {
|
||||
rpc Handle(HandleRequest) returns (HandleResponse);
|
||||
}
|
||||
message HandleRequest {
|
||||
string method = 1;
|
||||
string path = 2;
|
||||
string query = 3;
|
||||
map<string,string> req_header = 4;
|
||||
bytes req_body = 5;
|
||||
string client_ip = 6;
|
||||
}
|
||||
message HandleResponse {
|
||||
int32 status = 1;
|
||||
map<string,string> resp_header = 2;
|
||||
bytes resp_body = 3;
|
||||
string error = 4;
|
||||
}
|
||||
|
||||
// ── DashboardService(MID,依赖 recorder):只读管理界面 ──
|
||||
service DashboardService {
|
||||
rpc Render(RenderRequest) returns (RenderResponse);
|
||||
}
|
||||
message RenderRequest { string path = 1; string query = 2; }
|
||||
message RenderResponse {
|
||||
int32 status = 1;
|
||||
string content_type = 2;
|
||||
bytes body = 3;
|
||||
}
|
||||
Reference in New Issue
Block a user