1- """HTTP client for the GLiNER Ray Serve deployment.
1+ """HTTP client for the GLiNER Ray Serve deployment."""
22
33from typing import Any , Dict , List , Union , Optional
44
@@ -39,13 +39,52 @@ def __init__(
3939 max_concurrency: Maximum in-flight HTTP requests when predicting
4040 on a list of texts. Bounds the client-side thread pool.
4141 """
42- import ray # noqa: PLC0415
43- from ray import serve # noqa: PLC0415
42+ self .url = base_url .rstrip ("/" ) + route_prefix
43+ self .timeout = timeout
44+ self .max_concurrency = max_concurrency
4445
45- if not ray.is_initialized():
46- ray.init(address=ray_address, ignore_reinit_error=True)
47-
48- self._handle = serve.get_deployment_handle(deployment_name, "gliner")
46+ def _build_payload (
47+ self ,
48+ text : str ,
49+ labels : List [str ],
50+ relations : Optional [List [str ]],
51+ threshold : Optional [float ],
52+ relation_threshold : Optional [float ],
53+ flat_ner : bool ,
54+ multi_label : bool ,
55+ ) -> Dict [str , Any ]:
56+ """Build the JSON payload for a single prediction request."""
57+ payload : Dict [str , Any ] = {
58+ "text" : text ,
59+ "labels" : labels ,
60+ "flat_ner" : flat_ner ,
61+ "multi_label" : multi_label ,
62+ }
63+ if relations is not None :
64+ payload ["relations" ] = relations
65+ if threshold is not None :
66+ payload ["threshold" ] = threshold
67+ if relation_threshold is not None :
68+ payload ["relation_threshold" ] = relation_threshold
69+ return payload
70+
71+ def _post (self , payload : Dict [str , Any ]) -> Dict [str , Any ]:
72+ """Send a single POST request to the server."""
73+ import json # noqa: PLC0415
74+ import urllib .request # noqa: PLC0415
75+
76+ data = json .dumps (payload ).encode ()
77+ req = urllib .request .Request (
78+ self .url ,
79+ data = data ,
80+ headers = {"Content-Type" : "application/json" },
81+ method = "POST" ,
82+ )
83+ try :
84+ with urllib .request .urlopen (req , timeout = self .timeout ) as resp :
85+ return json .loads (resp .read ())
86+ except Exception as exc :
87+ raise GLiNERClientError (f"Request to { self .url } failed: { exc } " ) from exc
4988
5089 def predict (
5190 self ,
@@ -57,7 +96,7 @@ def predict(
5796 flat_ner : bool = True ,
5897 multi_label : bool = False ,
5998 ) -> Union [Dict [str , Any ], List [Dict [str , Any ]]]:
60- """ Blocking prediction . `` str `` in → `` dict `` out ; `` list `` in → `` list `` out ."""
99+ """Blocking prediction. ``str`` in -> ``dict`` out; ``list`` in -> ``list`` out."""
61100 single = isinstance (text , str )
62101 items = [text ] if single else list (text )
63102
@@ -69,27 +108,11 @@ def predict(
69108 for t in items
70109 ]
71110
72- Args:
73- text: Input text or list of texts.
74- labels: Entity type labels to extract.
75- relations: Relation type labels (for relex models).
76- threshold: Confidence threshold for entities.
77- relation_threshold: Confidence threshold for relations.
78- flat_ner: Whether to use flat NER.
79- multi_label: Whether to allow multiple labels per span.
80-
81- Returns:
82- Single result dict or list of result dicts containing:
83- - "entities": List of entity dicts
84- - "relations": List of relation dicts (if model supports)
85- """
86- if isinstance (text , list ):
87- refs = [
88- self ._handle .predict .remote (t , labels , relations , threshold , relation_threshold , flat_ner , multi_label )
89- for t in text
90- ]
91- return [ref .result () for ref in refs ]
111+ if len (payloads ) == 1 :
112+ results = [self ._post (payloads [0 ])]
92113 else :
114+ from concurrent .futures import ThreadPoolExecutor # noqa: PLC0415
115+
93116 workers = min (self .max_concurrency , len (payloads ))
94117 with ThreadPoolExecutor (max_workers = workers ) as pool :
95118 results = list (pool .map (self ._post , payloads ))
@@ -107,18 +130,15 @@ async def predict_async(
107130 multi_label : bool = False ,
108131 ) -> Union [Dict [str , Any ], List [Dict [str , Any ]]]:
109132 """Async version of predict."""
110- if isinstance (text , list ):
111- import asyncio # noqa: PLC0415
112-
113- refs = [
114- self ._handle .predict .remote (t , labels , relations , threshold , relation_threshold , flat_ner , multi_label )
115- for t in text
116- ]
117- results = await asyncio .gather (* refs )
118- return list (results )
119- else :
120- return await self ._handle .predict .remote (
121- text , labels , relations , threshold , relation_threshold , flat_ner , multi_label
133+ import asyncio # noqa: PLC0415
134+
135+ single = isinstance (text , str )
136+ items = [text ] if single else list (text )
137+
138+ payloads = [
139+ self ._build_payload (
140+ t , labels , relations , threshold , relation_threshold ,
141+ flat_ner , multi_label ,
122142 )
123143 for t in items
124144 ]
0 commit comments