Kristian Rickert created OPENNLP-1833:
-----------------------------------------
Summary: Add document-centric gRPC API — evolve opennlp-sandbox
POC with canonical OpenNlpDocument and AnalyzeDocument RPC
Key: OPENNLP-1833
URL: https://issues.apache.org/jira/browse/OPENNLP-1833
Project: OpenNLP
Issue Type: New Feature
Components: gRPC binding
Affects Versions: 3.0.0-M3
Reporter: Kristian Rickert
Assignee: Kristian Rickert
h3. Problem
Apache OpenNLP is primarily an *in-process Java library* (API, CLI, UIMA). The
README notes embedding in distributed pipelines (Flink, NiFi, Spark), but there
is *no standard wire contract* for cross-language clients or remote inference.
A proof-of-concept exists in the sandbox:
* *Repository:*
[https://github.com/apache/opennlp-sandbox/tree/main/opennlp-grpc]
* *Current scope:* Three separate gRPC services
({{{}SentenceDetectorService{}}}, {{{}TokenizerTaggerService{}}},
{{{}PosTaggerService{}}}) with string-based requests and {{model_hash}} per call
* *Gap:* No unified *document* message, no pipeline orchestration, no
NER/chunking/embeddings, and clients must chain multiple RPCs
Main OpenNLP ({{{}apache/opennlp{}}}) has *no gRPC modules* on {{{}main{}}}.
OpenNLP 3.0 brings thread-safe {{*ME}} classes (JDK 21+), which makes a
long-lived gRPC server practical. The {{opennlp-dl}} / {{opennlp-dl-gpu}}
modules already support ONNX inference (including sentence embeddings via
{{{}SentenceVectorsDL{}}}).
h3. Proposal
Evolve the sandbox POC into ASF-native modules (target: main repo after
consensus):
||Module||Purpose||
|{{opennlp-grpc-api}}|Protocol Buffers + generated stubs (Java first;
descriptors for other languages)|
|{{opennlp-grpc-server}}|gRPC server, model bundle registry, pipeline
orchestration|
|{{opennlp-grpc-examples}}|Sample clients (e.g. Python)|
*Core API change:* Introduce a canonical *{{OpenNlpDocument}}* message (1:1
text document in, enriched document out) and a primary *{{AnalyzeDocument}}*
RPC that runs a configurable NLP pipeline server-side—similar in spirit to the
existing UIMA {{OpenNlpTextAnalyzer}} composite, but as a language-neutral
contract.
*Package naming (proposed):* {{org.apache.opennlp.grpc.v1}}
h3. Non-goals (v1 RFC)
* Binary/PDF document parsing (Tika, etc.) — callers supply {{raw_text}}
* Training, evaluation, or model-update RPCs
* Embedding {{.bin}} model bytes in request messages (models remain
server-side)
* Authentication / multi-tenancy in the core API (deployment concern: mTLS,
reverse proxy)
* Coreference (documented in manual but not implemented in current codebase)
h3. Compatibility
* *Additive* Maven modules; no breaking changes to {{opennlp-api}} /
{{opennlp-runtime}}
* Sandbox granular services may be deprecated or moved to
{{opennlp.legacy.v1}} after migration
h3. Phased delivery (high level)
||Phase||Scope||
|*0*|This JIRA + community RFC (this ticket)|
|*1*|Design document + full {{.proto}} definitions (no server code required for
consensus)|
|*2+*|Implementation: orchestrator, server, tests, graduation from sandbox to
main repo|
|*Later*|GPU embeddings ({{{}opennlp-dl-gpu{}}}), optional OpenVINO/DJL
inference backends|
h3. Design highlights
# *Three proto layers (NLP-only):* domain types ({{{}OpenNlpDocument{}}}),
pipeline config ({{{}AnalysisProfile{}}}), service
({{{}OpenNlpAnalysisService{}}})
# *Offset contract:* All exported spans use *character offsets in the original
{{raw_text}}* ({{{}CHAR_DOCUMENT{}}}), half-open {{[start, end)}} matching
{{opennlp.tools.util.Span}}
# *Model bundles:* Replace per-RPC {{model_hash}} with {{ModelBundleRef}} +
server-defined profiles (reuse sandbox model discovery patterns)
# *Thread safety:* Leverage OpenNLP 3.0 thread-safe {{*ME}} instances cached
per model bundle
h3. Sample protobuf (illustrative — full spec in design doc)
The following is a *sketch* for discussion; field numbers and optional messages
may change during RFC.
{code:java|title=opennlp.proto}
syntax = "proto3";
package org.apache.opennlp.grpc.v1;
option java_package = "org.apache.opennlp.grpc.v1";
option java_multiple_files = true;
// --- Layer 1: Document ---
message OpenNlpDocument {
string doc_id = 1;
string raw_text = 2;
optional string detected_language = 3;
optional float language_confidence = 4;
repeated AnnotatedSentence sentences = 5;
map<string, string> metadata = 6;
}
message AnnotatedSentence {
CharSpan sentence_span = 1;
repeated Token tokens = 2;
repeated NamedEntity entities = 3;
}
message Token {
string text = 1;
CharSpan char_span = 2;
optional string pos_tag = 3;
}
message NamedEntity {
CharSpan char_span = 1;
string entity_type = 2;
optional double prob = 3;
}
message CharSpan {
int32 start = 1;
int32 end = 2;
CoordinateSpace space = 3;
optional string type = 4;
optional double prob = 5;
}
enum CoordinateSpace {
COORDINATE_SPACE_UNSPECIFIED = 0;
CHAR_DOCUMENT = 1;
}
// --- Layer 2: Pipeline ---
enum PipelineStep {
PIPELINE_STEP_UNSPECIFIED = 0;
LANGUAGE_DETECT = 1;
SENTENCE_DETECT = 2;
TOKENIZE = 3;
POS_TAG = 4;
NER = 5;
}
message AnalysisProfile {
string profile_id = 1;
repeated PipelineStep steps = 2;
ModelBundleRef model_bundle = 3;
}
message ModelBundleRef {
string bundle_id = 1;
}
message AnalysisOptions {
bool include_probabilities = 1;
bool clear_adaptive_data = 2;
}
// --- Layer 3: Service ---
service OpenNlpAnalysisService {
rpc AnalyzeDocument(AnalyzeDocumentRequest) returns (AnalyzeDocumentResponse);
rpc GetServiceInfo(GetServiceInfoRequest) returns (GetServiceInfoResponse);
}
message AnalyzeDocumentRequest {
OpenNlpDocument document = 1;
AnalysisProfile profile = 2;
AnalysisOptions options = 3;
}
message AnalyzeDocumentResponse {
OpenNlpDocument document = 1;
repeated ProcessingDiagnostic diagnostics = 2;
}
message ProcessingDiagnostic {
PipelineStep step = 1;
string message = 2;
DiagnosticSeverity severity = 3;
}
enum DiagnosticSeverity {
DIAGNOSTIC_SEVERITY_UNSPECIFIED = 0;
INFO = 1;
WARNING = 2;
ERROR = 3;
}
message GetServiceInfoRequest {}
message GetServiceInfoResponse {
string opennlp_version = 1;
string api_version = 2;
repeated string available_profile_ids = 3;
}
{code}
h3. Comparison: sandbox vs proposed
||Aspect||Sandbox POC||Proposed||
|Services|3 (sent / token / POS)|1 primary ({{{}OpenNlpAnalysisService{}}})|
|I/O|Strings + {{StringList}}|{{OpenNlpDocument}}|
|Models|{{model_hash}} per RPC|{{ModelBundleRef}} + profiles|
|Pipeline|Client-side chaining|Server-side {{AnalysisProfile}}|
|Package|{{package opennlp}}|{{org.apache.opennlp.grpc.v1}}|
h3. References
* Sandbox POC:
[https://github.com/apache/opennlp-sandbox/tree/main/opennlp-grpc]
* Current sandbox proto:
[https://github.com/apache/opennlp-sandbox/blob/main/opennlp-grpc/opennlp-grpc-api/opennlp.proto]
* UIMA composite pipeline:
{{opennlp-extensions/opennlp-uima/descriptors/OpenNlpTextAnalyzer.xml}}
* ONNX / GPU: {{{}opennlp-dl{}}}, {{{}opennlp-dl-gpu{}}}, {{SentenceVectorsDL}}
* Full design document (companion): {{docs/rfc/opennlp-grpc-design.md}} in
contributor branch or attachment
h3. Questions for the community
# Should v1 expose *only* {{{}AnalyzeDocument{}}}, or retain sandbox granular
RPCs under a legacy package?
# Target release: *3.0.x* (additive) vs {*}3.1{*}?
# Proto tooling: Maven {{protobuf-maven-plugin}} only, is a gradle build OK?
--
This message was sent by Atlassian Jira
(v8.20.10#820010)