tamar-model-client 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,11 @@
1
+ from .sync_client import ModelManagerClient
2
+ from .async_client import AsyncModelManagerClient
3
+ from .exceptions import ModelManagerClientError, ConnectionError, ValidationError
4
+
5
+ __all__ = [
6
+ "ModelManagerClient",
7
+ "AsyncModelManagerClient",
8
+ "ModelManagerClientError",
9
+ "ConnectionError",
10
+ "ValidationError",
11
+ ]
@@ -0,0 +1,419 @@
1
+ import asyncio
2
+ import atexit
3
+ import json
4
+ import logging
5
+ import os
6
+
7
+ import grpc
8
+ from typing import Optional, AsyncIterator, Union, Iterable
9
+
10
+ from openai import NOT_GIVEN
11
+ from pydantic import BaseModel
12
+
13
+ from .auth import JWTAuthHandler
14
+ from .enums import ProviderType, InvokeType
15
+ from .exceptions import ConnectionError, ValidationError
16
+ from .schemas import ModelRequest, ModelResponse, BatchModelRequest, BatchModelResponse
17
+ from .generated import model_service_pb2, model_service_pb2_grpc
18
+ from .schemas.inputs import GoogleGenAiInput, OpenAIResponsesInput, OpenAIChatCompletionsInput
19
+
20
+ if not logging.getLogger().hasHandlers():
21
+ # 配置日志格式
22
+ logging.basicConfig(
23
+ level=logging.INFO,
24
+ format="%(asctime)s [%(levelname)s] %(message)s",
25
+ )
26
+
27
+ logger = logging.getLogger(__name__)
28
+
29
+
30
+ class AsyncModelManagerClient:
31
+ def __init__(
32
+ self,
33
+ server_address: Optional[str] = None,
34
+ jwt_secret_key: Optional[str] = None,
35
+ jwt_token: Optional[str] = None,
36
+ default_payload: Optional[dict] = None,
37
+ token_expires_in: int = 3600,
38
+ max_retries: int = 3, # 最大重试次数
39
+ retry_delay: float = 1.0, # 初始重试延迟(秒)
40
+ ):
41
+ # 服务端地址
42
+ self.server_address = server_address or os.getenv("MODEL_MANAGER_SERVER_ADDRESS")
43
+ if not self.server_address:
44
+ raise ValueError("Server address must be provided via argument or environment variable.")
45
+ self.default_invoke_timeout = float(os.getenv("MODEL_MANAGER_SERVER_INVOKE_TIMEOUT", 30.0))
46
+
47
+ # JWT 配置
48
+ self.jwt_secret_key = jwt_secret_key or os.getenv("MODEL_MANAGER_SERVER_JWT_SECRET_KEY")
49
+ self.jwt_handler = JWTAuthHandler(self.jwt_secret_key)
50
+ self.jwt_token = jwt_token # 用户传入的 Token(可选)
51
+ self.default_payload = default_payload
52
+ self.token_expires_in = token_expires_in
53
+
54
+ # === TLS/Authority 配置 ===
55
+ self.use_tls = os.getenv("MODEL_MANAGER_SERVER_GRPC_USE_TLS", "true").lower() == "true"
56
+ self.default_authority = os.getenv("MODEL_MANAGER_SERVER_GRPC_DEFAULT_AUTHORITY")
57
+
58
+ # === 重试配置 ===
59
+ self.max_retries = max_retries if max_retries is not None else int(
60
+ os.getenv("MODEL_MANAGER_SERVER_GRPC_MAX_RETRIES", 3))
61
+ self.retry_delay = retry_delay if retry_delay is not None else float(
62
+ os.getenv("MODEL_MANAGER_SERVER_GRPC_RETRY_DELAY", 1.0))
63
+
64
+ # === gRPC 通道相关 ===
65
+ self.channel: Optional[grpc.aio.Channel] = None
66
+ self.stub: Optional[model_service_pb2_grpc.ModelServiceStub] = None
67
+ self._closed = False
68
+ atexit.register(self._safe_sync_close) # 注册进程退出自动关闭
69
+
70
+ def _build_auth_metadata(self) -> list:
71
+ if not self.jwt_token and self.jwt_handler:
72
+ self.jwt_token = self.jwt_handler.encode_token(self.default_payload, expires_in=self.token_expires_in)
73
+ return [("authorization", f"Bearer {self.jwt_token}")] if self.jwt_token else []
74
+
75
+ async def _ensure_initialized(self):
76
+ """初始化 gRPC 通道,支持 TLS 与重试机制"""
77
+ if self.channel and self.stub:
78
+ return
79
+
80
+ retry_count = 0
81
+ options = []
82
+ if self.default_authority:
83
+ options.append(("grpc.default_authority", self.default_authority))
84
+
85
+ while retry_count <= self.max_retries:
86
+ try:
87
+ if self.use_tls:
88
+ credentials = grpc.ssl_channel_credentials()
89
+ self.channel = grpc.aio.secure_channel(
90
+ self.server_address,
91
+ credentials,
92
+ options=options
93
+ )
94
+ logger.info("🔐 Using secure gRPC channel (TLS enabled)")
95
+ else:
96
+ self.channel = grpc.aio.insecure_channel(
97
+ self.server_address,
98
+ options=options
99
+ )
100
+ logger.info("🔓 Using insecure gRPC channel (TLS disabled)")
101
+ await self.channel.channel_ready()
102
+ self.stub = model_service_pb2_grpc.ModelServiceStub(self.channel)
103
+ logger.info(f"✅ gRPC channel initialized to {self.server_address}")
104
+ return
105
+ except grpc.FutureTimeoutError as e:
106
+ logger.warning(f"❌ gRPC channel initialization timed out: {str(e)}")
107
+ except grpc.RpcError as e:
108
+ logger.warning(f"❌ gRPC channel initialization failed: {str(e)}")
109
+ except Exception as e:
110
+ logger.warning(f"❌ Unexpected error during channel initialization: {str(e)}")
111
+
112
+ retry_count += 1
113
+ if retry_count > self.max_retries:
114
+ raise ConnectionError(f"❌ Failed to initialize gRPC channel after {self.max_retries} retries.")
115
+
116
+ # 指数退避:延迟时间 = retry_delay * (2 ^ (retry_count - 1))
117
+ delay = self.retry_delay * (2 ** (retry_count - 1))
118
+ logger.info(f"🚀 Retrying connection (attempt {retry_count}/{self.max_retries}) after {delay:.2f}s delay...")
119
+ await asyncio.sleep(delay)
120
+
121
+ async def _stream(self, model_request, metadata, invoke_timeout) -> AsyncIterator[ModelResponse]:
122
+ try:
123
+ async for response in self.stub.Invoke(model_request, metadata=metadata, timeout=invoke_timeout):
124
+ yield ModelResponse(
125
+ content=response.content,
126
+ usage=json.loads(response.usage) if response.usage else None,
127
+ raw_response=json.loads(response.raw_response) if response.raw_response else None,
128
+ error=response.error or None,
129
+ )
130
+ except grpc.RpcError as e:
131
+ raise ConnectionError(f"gRPC call failed: {str(e)}")
132
+ except Exception as e:
133
+ raise ValidationError(f"Invalid input: {str(e)}")
134
+
135
+ async def invoke(self, model_request: ModelRequest, timeout: Optional[float] = None) -> Union[
136
+ ModelResponse, AsyncIterator[ModelResponse]]:
137
+ """
138
+ 通用调用模型方法。
139
+
140
+ Args:
141
+ model_request: ModelRequest 对象,包含请求参数。
142
+
143
+ Yields:
144
+ ModelResponse: 支持流式或非流式的模型响应
145
+
146
+ Raises:
147
+ ValidationError: 输入验证失败。
148
+ ConnectionError: 连接服务端失败。
149
+ """
150
+ await self._ensure_initialized()
151
+
152
+ if not self.default_payload:
153
+ self.default_payload = {
154
+ "org_id": model_request.user_context.org_id or "",
155
+ "user_id": model_request.user_context.user_id or ""
156
+ }
157
+
158
+ # 动态根据 provider/invoke_type 决定使用哪个 input 字段
159
+ try:
160
+ if model_request.provider == ProviderType.GOOGLE:
161
+ allowed_fields = GoogleGenAiInput.model_fields.keys()
162
+ elif model_request.provider in {ProviderType.OPENAI, ProviderType.AZURE}:
163
+ if model_request.invoke_type in {InvokeType.RESPONSES, InvokeType.GENERATION}:
164
+ allowed_fields = OpenAIResponsesInput.model_fields.keys()
165
+ elif model_request.invoke_type == InvokeType.CHAT_COMPLETIONS:
166
+ allowed_fields = OpenAIChatCompletionsInput.model_fields.keys()
167
+ else:
168
+ raise ValueError(f"暂不支持的调用类型: {model_request.invoke_type}")
169
+ else:
170
+ raise ValueError(f"暂不支持的提供商: {model_request.provider}")
171
+
172
+ # 将 ModelRequest 转 dict,过滤只保留 base + allowed 的字段
173
+ model_request_dict = model_request.model_dump(exclude_unset=True)
174
+
175
+ grpc_request_kwargs = {}
176
+ for field in allowed_fields:
177
+ if field in model_request_dict:
178
+ value = model_request_dict[field]
179
+
180
+ # Skip fields with NotGiven or None (unless explicitly allowed)
181
+ if value is NOT_GIVEN or value is None:
182
+ continue
183
+
184
+ # 特别处理:如果是自定义的 BaseModel 或特定类型
185
+ if isinstance(value, BaseModel):
186
+ grpc_request_kwargs[field] = value.model_dump()
187
+ # 如果是 OpenAI / Google 里的自定义对象,通常有 dict() 方法
188
+ elif hasattr(value, "dict") and callable(value.dict):
189
+ grpc_request_kwargs[field] = value.dict()
190
+ # 如果是 list,需要处理里面元素也是自定义对象的情况
191
+ elif isinstance(value, Iterable) and not isinstance(value, (str, bytes, dict)):
192
+ new_list = []
193
+ for item in value:
194
+ if isinstance(item, BaseModel):
195
+ new_list.append(item.model_dump())
196
+ elif hasattr(item, "dict") and callable(item.dict):
197
+ new_list.append(item.dict())
198
+ elif isinstance(item, dict):
199
+ # Handle nested dictionaries
200
+ nested_dict = {}
201
+ for k, v in item.items():
202
+ if isinstance(v, BaseModel):
203
+ nested_dict[k] = v.model_dump()
204
+ elif hasattr(v, "dict") and callable(v.dict):
205
+ nested_dict[k] = v.dict()
206
+ else:
207
+ nested_dict[k] = v
208
+ new_list.append(nested_dict)
209
+ else:
210
+ new_list.append(item)
211
+ grpc_request_kwargs[field] = new_list
212
+ # 如果是 dict,同理处理内部元素
213
+ elif isinstance(value, dict):
214
+ new_dict = {}
215
+ for k, v in value.items():
216
+ if isinstance(v, BaseModel):
217
+ new_dict[k] = v.model_dump()
218
+ elif hasattr(v, "dict") and callable(v.dict):
219
+ new_dict[k] = v.dict()
220
+ else:
221
+ new_dict[k] = v
222
+ grpc_request_kwargs[field] = new_dict
223
+ else:
224
+ grpc_request_kwargs[field] = value
225
+
226
+ request = model_service_pb2.ModelRequestItem(
227
+ provider=model_request.provider.value,
228
+ channel=model_request.channel.value,
229
+ invoke_type=model_request.invoke_type.value,
230
+ stream=model_request.stream or False,
231
+ org_id=model_request.user_context.org_id or "",
232
+ user_id=model_request.user_context.user_id or "",
233
+ client_type=model_request.user_context.client_type or "",
234
+ extra=grpc_request_kwargs
235
+ )
236
+
237
+ except Exception as e:
238
+ raise ValueError(f"构建请求失败: {str(e)}") from e
239
+
240
+ metadata = self._build_auth_metadata()
241
+
242
+ invoke_timeout = timeout or self.default_invoke_timeout
243
+ if model_request.stream:
244
+ return self._stream(request, metadata, invoke_timeout)
245
+ else:
246
+ async for response in self.stub.Invoke(request, metadata=metadata, timeout=invoke_timeout):
247
+ return ModelResponse(
248
+ content=response.content,
249
+ usage=json.loads(response.usage) if response.usage else None,
250
+ raw_response=json.loads(response.raw_response) if response.raw_response else None,
251
+ error=response.error or None,
252
+ custom_id=None,
253
+ request_id=response.request_id if response.request_id else None,
254
+ )
255
+
256
+ async def invoke_batch(self, batch_request_model: BatchModelRequest, timeout: Optional[float] = None) -> \
257
+ BatchModelResponse:
258
+ """
259
+ 批量模型调用接口
260
+
261
+ Args:
262
+ batch_request_model: 多条 BatchModelRequest 输入
263
+ timeout: 调用超时,单位秒
264
+
265
+ Returns:
266
+ BatchModelResponse: 批量请求的结果
267
+ """
268
+ await self._ensure_initialized()
269
+
270
+ if not self.default_payload:
271
+ self.default_payload = {
272
+ "org_id": batch_request_model.user_context.org_id or "",
273
+ "user_id": batch_request_model.user_context.user_id or ""
274
+ }
275
+
276
+ metadata = self._build_auth_metadata()
277
+
278
+ # 构造批量请求
279
+ items = []
280
+ for model_request_item in batch_request_model.items:
281
+ # 动态根据 provider/invoke_type 决定使用哪个 input 字段
282
+ try:
283
+ if model_request_item.provider == ProviderType.GOOGLE:
284
+ allowed_fields = GoogleGenAiInput.model_fields.keys()
285
+ elif model_request_item.provider in {ProviderType.OPENAI, ProviderType.AZURE}:
286
+ if model_request_item.invoke_type in {InvokeType.RESPONSES, InvokeType.GENERATION}:
287
+ allowed_fields = OpenAIResponsesInput.model_fields.keys()
288
+ elif model_request_item.invoke_type == InvokeType.CHAT_COMPLETIONS:
289
+ allowed_fields = OpenAIChatCompletionsInput.model_fields.keys()
290
+ else:
291
+ raise ValueError(f"暂不支持的调用类型: {model_request_item.invoke_type}")
292
+ else:
293
+ raise ValueError(f"暂不支持的提供商: {model_request_item.provider}")
294
+
295
+ # 将 ModelRequest 转 dict,过滤只保留 base + allowed 的字段
296
+ model_request_dict = model_request_item.model_dump(exclude_unset=True)
297
+
298
+ grpc_request_kwargs = {}
299
+ for field in allowed_fields:
300
+ if field in model_request_dict:
301
+ value = model_request_dict[field]
302
+
303
+ # Skip fields with NotGiven or None (unless explicitly allowed)
304
+ if value is NOT_GIVEN or value is None:
305
+ continue
306
+
307
+ # 特别处理:如果是自定义的 BaseModel 或特定类型
308
+ if isinstance(value, BaseModel):
309
+ grpc_request_kwargs[field] = value.model_dump()
310
+ # 如果是 OpenAI / Google 里的自定义对象,通常有 dict() 方法
311
+ elif hasattr(value, "dict") and callable(value.dict):
312
+ grpc_request_kwargs[field] = value.dict()
313
+ # 如果是 list,需要处理里面元素也是自定义对象的情况
314
+ elif isinstance(value, Iterable) and not isinstance(value, (str, bytes, dict)):
315
+ new_list = []
316
+ for item in value:
317
+ if isinstance(item, BaseModel):
318
+ new_list.append(item.model_dump())
319
+ elif hasattr(item, "dict") and callable(item.dict):
320
+ new_list.append(item.dict())
321
+ elif isinstance(item, dict):
322
+ # Handle nested dictionaries
323
+ nested_dict = {}
324
+ for k, v in item.items():
325
+ if isinstance(v, BaseModel):
326
+ nested_dict[k] = v.model_dump()
327
+ elif hasattr(v, "dict") and callable(v.dict):
328
+ nested_dict[k] = v.dict()
329
+ else:
330
+ nested_dict[k] = v
331
+ new_list.append(nested_dict)
332
+ else:
333
+ new_list.append(item)
334
+ grpc_request_kwargs[field] = new_list
335
+ # 如果是 dict,同理处理内部元素
336
+ elif isinstance(value, dict):
337
+ new_dict = {}
338
+ for k, v in value.items():
339
+ if isinstance(v, BaseModel):
340
+ new_dict[k] = v.model_dump()
341
+ elif hasattr(v, "dict") and callable(v.dict):
342
+ new_dict[k] = v.dict()
343
+ else:
344
+ new_dict[k] = v
345
+ grpc_request_kwargs[field] = new_dict
346
+ else:
347
+ grpc_request_kwargs[field] = value
348
+
349
+ items.append(model_service_pb2.ModelRequestItem(
350
+ provider=model_request_item.provider.value,
351
+ channel=model_request_item.channel.value,
352
+ invoke_type=model_request_item.invoke_type.value,
353
+ stream=model_request_item.stream or False,
354
+ custom_id=model_request_item.custom_id or "",
355
+ priority=model_request_item.priority or 1,
356
+ org_id=batch_request_model.user_context.org_id or "",
357
+ user_id=batch_request_model.user_context.user_id or "",
358
+ client_type=batch_request_model.user_context.client_type or "",
359
+ extra=grpc_request_kwargs,
360
+ ))
361
+
362
+ except Exception as e:
363
+ raise ValueError(f"构建请求失败: {str(e)},item={model_request_item.custom_id}") from e
364
+
365
+ try:
366
+ # 超时处理逻辑
367
+ invoke_timeout = timeout or self.default_invoke_timeout
368
+
369
+ # 调用 gRPC 接口
370
+ response = await self.stub.BatchInvoke(
371
+ model_service_pb2.ModelRequest(items=items),
372
+ timeout=invoke_timeout,
373
+ metadata=metadata
374
+ )
375
+
376
+ result = []
377
+ for res_item in response.items:
378
+ result.append(ModelResponse(
379
+ content=res_item.content,
380
+ usage=json.loads(res_item.usage) if res_item.usage else None,
381
+ raw_response=json.loads(res_item.raw_response) if res_item.raw_response else None,
382
+ error=res_item.error or None,
383
+ custom_id=res_item.custom_id if res_item.custom_id else None
384
+ ))
385
+ return BatchModelResponse(
386
+ request_id=response.request_id if response.request_id else None,
387
+ responses=result
388
+ )
389
+ except grpc.RpcError as e:
390
+ raise ConnectionError(f"BatchInvoke failed: {str(e)}")
391
+
392
+ async def close(self):
393
+ """关闭 gRPC 通道"""
394
+ if self.channel and not self._closed:
395
+ await self.channel.close()
396
+ self._closed = True
397
+ await self.channel.close()
398
+ logger.info("✅ gRPC channel closed")
399
+
400
+ def _safe_sync_close(self):
401
+ """进程退出时自动关闭 channel(事件循环处理兼容)"""
402
+ if self.channel and not self._closed:
403
+ try:
404
+ loop = asyncio.get_event_loop()
405
+ if loop.is_running():
406
+ loop.create_task(self.close())
407
+ else:
408
+ loop.run_until_complete(self.close())
409
+ except Exception as e:
410
+ logger.warning(f"❌ gRPC channel close failed at exit: {e}")
411
+
412
+ async def __aenter__(self):
413
+ """支持 async with 自动初始化连接"""
414
+ await self._ensure_initialized()
415
+ return self
416
+
417
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
418
+ """支持 async with 自动关闭连接"""
419
+ await self.close()
@@ -0,0 +1,14 @@
1
+ import time
2
+ import jwt
3
+
4
+
5
+ # JWT 处理类
6
+ class JWTAuthHandler:
7
+ def __init__(self, secret_key: str):
8
+ self.secret_key = secret_key
9
+
10
+ def encode_token(self, payload: dict, expires_in: int = 3600) -> str:
11
+ """生成带过期时间的 JWT Token"""
12
+ payload = payload.copy()
13
+ payload["exp"] = int(time.time()) + expires_in
14
+ return jwt.encode(payload, self.secret_key, algorithm="HS256")
@@ -0,0 +1,8 @@
1
+ """
2
+ 枚举类型定义
3
+ """
4
+ from .channel import Channel
5
+ from .invoke import InvokeType
6
+ from .providers import ProviderType
7
+
8
+ __all__ = ["ProviderType", "InvokeType", "Channel"]
@@ -0,0 +1,11 @@
1
+ from enum import Enum
2
+
3
+
4
+ class Channel(str, Enum):
5
+ """渠道枚举"""
6
+ OPENAI = "openai"
7
+ VERTEXAI = "vertexai"
8
+ AI_STUDIO = "ai-studio"
9
+
10
+ # 默认的
11
+ NORMAL = "normal"
@@ -0,0 +1,10 @@
1
+ from enum import Enum
2
+
3
+
4
+ class InvokeType(str, Enum):
5
+ """模型调用类型枚举"""
6
+ RESPONSES = "responses"
7
+ CHAT_COMPLETIONS = "chat-completions"
8
+
9
+ # 默认的
10
+ GENERATION = "generation"
@@ -0,0 +1,8 @@
1
+ from enum import Enum
2
+
3
+
4
+ class ProviderType(str, Enum):
5
+ """模型提供商类型枚举"""
6
+ OPENAI = "openai"
7
+ GOOGLE = "google"
8
+ AZURE = "azure"
@@ -0,0 +1,11 @@
1
+ class ModelManagerClientError(Exception):
2
+ """Base exception for Model Manager Client errors"""
3
+ pass
4
+
5
+ class ConnectionError(ModelManagerClientError):
6
+ """Raised when connection to gRPC server fails"""
7
+ pass
8
+
9
+ class ValidationError(ModelManagerClientError):
10
+ """Raised when input validation fails"""
11
+ pass
File without changes
@@ -0,0 +1,45 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
3
+ # NO CHECKED-IN PROTOBUF GENCODE
4
+ # source: model_service.proto
5
+ # Protobuf Python Version: 5.29.0
6
+ """Generated protocol buffer code."""
7
+ from google.protobuf import descriptor as _descriptor
8
+ from google.protobuf import descriptor_pool as _descriptor_pool
9
+ from google.protobuf import runtime_version as _runtime_version
10
+ from google.protobuf import symbol_database as _symbol_database
11
+ from google.protobuf.internal import builder as _builder
12
+ _runtime_version.ValidateProtobufRuntimeVersion(
13
+ _runtime_version.Domain.PUBLIC,
14
+ 5,
15
+ 29,
16
+ 0,
17
+ '',
18
+ 'model_service.proto'
19
+ )
20
+ # @@protoc_insertion_point(imports)
21
+
22
+ _sym_db = _symbol_database.Default()
23
+
24
+
25
+ from google.protobuf import struct_pb2 as google_dot_protobuf_dot_struct__pb2
26
+
27
+
28
+ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x13model_service.proto\x12\rmodel_service\x1a\x1cgoogle/protobuf/struct.proto\"\x82\x02\n\x10ModelRequestItem\x12\x10\n\x08provider\x18\x01 \x01(\t\x12\x0f\n\x07\x63hannel\x18\x02 \x01(\t\x12\x13\n\x0binvoke_type\x18\x03 \x01(\t\x12\x0e\n\x06stream\x18\x04 \x01(\x08\x12\x0e\n\x06org_id\x18\x05 \x01(\t\x12\x0f\n\x07user_id\x18\x06 \x01(\t\x12\x13\n\x0b\x63lient_type\x18\x07 \x01(\t\x12\x15\n\x08priority\x18\x08 \x01(\x05H\x00\x88\x01\x01\x12\x16\n\tcustom_id\x18\t \x01(\tH\x01\x88\x01\x01\x12&\n\x05\x65xtra\x18\n \x01(\x0b\x32\x17.google.protobuf.StructB\x0b\n\t_priorityB\x0c\n\n_custom_id\">\n\x0cModelRequest\x12.\n\x05items\x18\x01 \x03(\x0b\x32\x1f.model_service.ModelRequestItem\"\xa6\x01\n\x11ModelResponseItem\x12\x0f\n\x07\x63ontent\x18\x01 \x01(\t\x12\r\n\x05usage\x18\x02 \x01(\t\x12\x14\n\x0craw_response\x18\x03 \x01(\t\x12\r\n\x05\x65rror\x18\x04 \x01(\t\x12\x16\n\tcustom_id\x18\x05 \x01(\tH\x00\x88\x01\x01\x12\x17\n\nrequest_id\x18\x06 \x01(\tH\x01\x88\x01\x01\x42\x0c\n\n_custom_idB\r\n\x0b_request_id\"T\n\rModelResponse\x12\x12\n\nrequest_id\x18\x01 \x01(\t\x12/\n\x05items\x18\x02 \x03(\x0b\x32 .model_service.ModelResponseItem2\xa7\x01\n\x0cModelService\x12M\n\x06Invoke\x12\x1f.model_service.ModelRequestItem\x1a .model_service.ModelResponseItem0\x01\x12H\n\x0b\x42\x61tchInvoke\x12\x1b.model_service.ModelRequest\x1a\x1c.model_service.ModelResponseb\x06proto3')
29
+
30
+ _globals = globals()
31
+ _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
32
+ _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'model_service_pb2', _globals)
33
+ if not _descriptor._USE_C_DESCRIPTORS:
34
+ DESCRIPTOR._loaded_options = None
35
+ _globals['_MODELREQUESTITEM']._serialized_start=69
36
+ _globals['_MODELREQUESTITEM']._serialized_end=327
37
+ _globals['_MODELREQUEST']._serialized_start=329
38
+ _globals['_MODELREQUEST']._serialized_end=391
39
+ _globals['_MODELRESPONSEITEM']._serialized_start=394
40
+ _globals['_MODELRESPONSEITEM']._serialized_end=560
41
+ _globals['_MODELRESPONSE']._serialized_start=562
42
+ _globals['_MODELRESPONSE']._serialized_end=646
43
+ _globals['_MODELSERVICE']._serialized_start=649
44
+ _globals['_MODELSERVICE']._serialized_end=816
45
+ # @@protoc_insertion_point(module_scope)