@effect-uai/jina 0.12.1 → 0.14.0
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.
- package/dist/JinaReranker.d.mts +53 -0
- package/dist/JinaReranker.d.mts.map +1 -0
- package/dist/JinaReranker.mjs +120 -0
- package/dist/JinaReranker.mjs.map +1 -0
- package/dist/index.d.mts +3 -2
- package/dist/index.mjs +2 -1
- package/dist/models.d.mts +12 -1
- package/dist/models.d.mts.map +1 -1
- package/package.json +6 -2
- package/src/JinaReranker.ts +203 -0
- package/src/index.ts +1 -0
- package/src/models.ts +17 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { JinaRerankerModel } from "./models.mjs";
|
|
2
|
+
import { Context, Effect, Layer, Redacted } from "effect";
|
|
3
|
+
import { HttpClient } from "effect/unstable/http";
|
|
4
|
+
import * as AiError from "@effect-uai/core/AiError";
|
|
5
|
+
import { CommonRerankRequest, RerankResponse, Reranker } from "@effect-uai/core/Reranker";
|
|
6
|
+
import { ImageSource } from "@effect-uai/core/Image";
|
|
7
|
+
declare namespace JinaReranker_d_exports {
|
|
8
|
+
export { Config, JinaRerankDocument, JinaRerankRequest, JinaReranker, JinaRerankerService, layer, make };
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* A candidate document. Text models take strings; `jina-reranker-m0` also
|
|
12
|
+
* takes `{ text }` / `{ image }`. One entry is one modality, and image
|
|
13
|
+
* *queries* are local-inference only, so `query` stays a string.
|
|
14
|
+
*/
|
|
15
|
+
type JinaRerankDocument = string | {
|
|
16
|
+
readonly text: string;
|
|
17
|
+
} | {
|
|
18
|
+
readonly image: ImageSource;
|
|
19
|
+
};
|
|
20
|
+
/** Widens {@link CommonRerankRequest} with Jina's multimodal documents. */
|
|
21
|
+
type JinaRerankRequest = Omit<CommonRerankRequest, "model" | "documents"> & {
|
|
22
|
+
readonly model: JinaRerankerModel;
|
|
23
|
+
readonly documents: ReadonlyArray<JinaRerankDocument>;
|
|
24
|
+
};
|
|
25
|
+
type JinaRerankerService = {
|
|
26
|
+
readonly rerank: (request: JinaRerankRequest) => Effect.Effect<RerankResponse, AiError.AiError>;
|
|
27
|
+
};
|
|
28
|
+
declare const JinaReranker_base: Context.ServiceClass<JinaReranker, "@betalyra/effect-uai/providers/jina/JinaReranker", JinaRerankerService>;
|
|
29
|
+
/**
|
|
30
|
+
* Provider-typed service tag. Yield this for multimodal documents; yield the
|
|
31
|
+
* generic `Reranker` tag for provider-portable code. Both are registered by
|
|
32
|
+
* {@link layer}.
|
|
33
|
+
*/
|
|
34
|
+
declare class JinaReranker extends JinaReranker_base {}
|
|
35
|
+
type Config = {
|
|
36
|
+
readonly apiKey: Redacted.Redacted;
|
|
37
|
+
readonly baseUrl?: string;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Build a `JinaRerankerService` value. For Layer-based setup, prefer
|
|
41
|
+
* {@link layer}.
|
|
42
|
+
*/
|
|
43
|
+
declare const make: (cfg: Config) => Effect.Effect<JinaRerankerService, never, HttpClient.HttpClient>;
|
|
44
|
+
/**
|
|
45
|
+
* Layer registering both the provider-typed `JinaReranker` tag and the generic
|
|
46
|
+
* `Reranker` tag over one implementation. A `CommonRerankRequest` is
|
|
47
|
+
* structurally a `JinaRerankRequest` with string documents, so the generic
|
|
48
|
+
* registration forwards directly.
|
|
49
|
+
*/
|
|
50
|
+
declare const layer: (cfg: Config) => Layer.Layer<JinaReranker | Reranker, never, HttpClient.HttpClient>;
|
|
51
|
+
//#endregion
|
|
52
|
+
export { Config, JinaRerankDocument, JinaRerankRequest, JinaReranker, JinaRerankerService, layer, make, JinaReranker_d_exports as t };
|
|
53
|
+
//# sourceMappingURL=JinaReranker.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"JinaReranker.d.mts","names":[],"sources":["../src/JinaReranker.ts"],"mappings":";;;;;;;;;;;;;;KAsBY;WAEG;;WACA,OAAO;;;KAGV,oBAAoB,KAAK;WAC1B,OAAO;WACP,WAAW,cAAc;;KAGxB;WACD,SAAS,SAAS,sBAAsB,OAAO,OAAO,gBAAgB,QAAQ;;;;;;;;cAQ5E,qBAAqB;KAItB;WACD,QAAQ,SAAS;WACjB;;;;;;cAmIE,OAAI,KACV,WACJ,OAAO,OAAO,4BAA4B,WAAW;;;;;;;cAY3C,QAAK,KACX,WACJ,MAAM,MAAM,eAAe,iBAAiB,WAAW"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
|
|
2
|
+
import { Context, Effect, Encoding, Layer, Match, Schema } from "effect";
|
|
3
|
+
import { HttpClient, HttpClientRequest } from "effect/unstable/http";
|
|
4
|
+
import * as AiError from "@effect-uai/core/AiError";
|
|
5
|
+
import { Reranker } from "@effect-uai/core/Reranker";
|
|
6
|
+
//#region src/JinaReranker.ts
|
|
7
|
+
var JinaReranker_exports = /* @__PURE__ */ __exportAll({
|
|
8
|
+
JinaReranker: () => JinaReranker,
|
|
9
|
+
layer: () => layer,
|
|
10
|
+
make: () => make
|
|
11
|
+
});
|
|
12
|
+
/**
|
|
13
|
+
* Provider-typed service tag. Yield this for multimodal documents; yield the
|
|
14
|
+
* generic `Reranker` tag for provider-portable code. Both are registered by
|
|
15
|
+
* {@link layer}.
|
|
16
|
+
*/
|
|
17
|
+
var JinaReranker = class extends Context.Service()("@betalyra/effect-uai/providers/jina/JinaReranker") {};
|
|
18
|
+
const imageSourceToWire = Match.type().pipe(Match.tag("url", (s) => ({ image: s.url })), Match.tag("base64", (s) => ({ image: s.base64 })), Match.tag("bytes", (s) => ({ image: Encoding.encodeBase64(s.bytes) })), Match.exhaustive);
|
|
19
|
+
const documentToWire = Match.type().pipe(Match.when(Match.string, (s) => s), Match.when({ text: Match.string }, ({ text }) => ({ text })), Match.when({ image: Match.any }, ({ image }) => imageSourceToWire(image)), Match.exhaustive);
|
|
20
|
+
const buildBody = (request) => ({
|
|
21
|
+
model: request.model,
|
|
22
|
+
query: request.query,
|
|
23
|
+
documents: request.documents.map(documentToWire),
|
|
24
|
+
...request.topN !== void 0 && { top_n: request.topN }
|
|
25
|
+
});
|
|
26
|
+
const WireResult = Schema.Struct({
|
|
27
|
+
index: Schema.Number,
|
|
28
|
+
relevance_score: Schema.Number
|
|
29
|
+
});
|
|
30
|
+
const WireUsage = Schema.Struct({ total_tokens: Schema.optional(Schema.Number) });
|
|
31
|
+
const WireResponse = Schema.Struct({
|
|
32
|
+
model: Schema.optional(Schema.String),
|
|
33
|
+
usage: Schema.optional(WireUsage),
|
|
34
|
+
results: Schema.Array(WireResult)
|
|
35
|
+
});
|
|
36
|
+
const usageOf = (usage) => usage?.total_tokens !== void 0 ? { totalTokens: usage.total_tokens } : {};
|
|
37
|
+
const toResponse = (wire) => ({
|
|
38
|
+
results: [...wire.results].sort((a, b) => b.relevance_score - a.relevance_score).map((r) => ({
|
|
39
|
+
index: r.index,
|
|
40
|
+
score: r.relevance_score
|
|
41
|
+
})),
|
|
42
|
+
usage: usageOf(wire.usage)
|
|
43
|
+
});
|
|
44
|
+
const transportFailure = (cause) => new AiError.Unavailable({
|
|
45
|
+
provider: "jina",
|
|
46
|
+
raw: cause
|
|
47
|
+
});
|
|
48
|
+
const httpStatusError = (status, body) => {
|
|
49
|
+
const provider = "jina";
|
|
50
|
+
const raw = body;
|
|
51
|
+
if (status === 429) return new AiError.RateLimited({
|
|
52
|
+
provider,
|
|
53
|
+
raw
|
|
54
|
+
});
|
|
55
|
+
if (status === 408 || status === 504) return new AiError.Timeout({
|
|
56
|
+
provider,
|
|
57
|
+
raw
|
|
58
|
+
});
|
|
59
|
+
if (status === 401) return new AiError.AuthFailed({
|
|
60
|
+
provider,
|
|
61
|
+
subtype: "auth",
|
|
62
|
+
raw
|
|
63
|
+
});
|
|
64
|
+
if (status === 403) return new AiError.AuthFailed({
|
|
65
|
+
provider,
|
|
66
|
+
subtype: "permission",
|
|
67
|
+
raw
|
|
68
|
+
});
|
|
69
|
+
if (status === 402) return new AiError.AuthFailed({
|
|
70
|
+
provider,
|
|
71
|
+
subtype: "billing",
|
|
72
|
+
raw
|
|
73
|
+
});
|
|
74
|
+
if (status === 413) return new AiError.ContextLengthExceeded({
|
|
75
|
+
provider,
|
|
76
|
+
raw
|
|
77
|
+
});
|
|
78
|
+
if (status >= 500) return new AiError.Unavailable({
|
|
79
|
+
provider,
|
|
80
|
+
status,
|
|
81
|
+
raw
|
|
82
|
+
});
|
|
83
|
+
return new AiError.InvalidRequest({
|
|
84
|
+
provider,
|
|
85
|
+
raw
|
|
86
|
+
});
|
|
87
|
+
};
|
|
88
|
+
const baseUrl = (cfg) => cfg.baseUrl ?? "https://api.jina.ai/v1";
|
|
89
|
+
const postRerank = (cfg, body) => Effect.gen(function* () {
|
|
90
|
+
const client = yield* HttpClient.HttpClient;
|
|
91
|
+
const httpRequest = HttpClientRequest.post(`${baseUrl(cfg)}/rerank`).pipe(HttpClientRequest.bearerToken(cfg.apiKey), HttpClientRequest.bodyJsonUnsafe(body));
|
|
92
|
+
const response = yield* client.execute(httpRequest).pipe(Effect.mapError(transportFailure));
|
|
93
|
+
if (response.status >= 400) {
|
|
94
|
+
const text = yield* response.text.pipe(Effect.orElseSucceed(() => ""));
|
|
95
|
+
return yield* httpStatusError(response.status, text);
|
|
96
|
+
}
|
|
97
|
+
const json = yield* response.json.pipe(Effect.mapError(transportFailure));
|
|
98
|
+
return yield* Schema.decodeUnknownEffect(WireResponse)(json).pipe(Effect.mapError(transportFailure));
|
|
99
|
+
});
|
|
100
|
+
const rerankImpl = (cfg) => (request) => Effect.map(postRerank(cfg, buildBody(request)), toResponse);
|
|
101
|
+
/**
|
|
102
|
+
* Build a `JinaRerankerService` value. For Layer-based setup, prefer
|
|
103
|
+
* {@link layer}.
|
|
104
|
+
*/
|
|
105
|
+
const make = (cfg) => Effect.map(HttpClient.HttpClient, (client) => ({ rerank: (request) => rerankImpl(cfg)(request).pipe(Effect.provideService(HttpClient.HttpClient, client)) }));
|
|
106
|
+
/**
|
|
107
|
+
* Layer registering both the provider-typed `JinaReranker` tag and the generic
|
|
108
|
+
* `Reranker` tag over one implementation. A `CommonRerankRequest` is
|
|
109
|
+
* structurally a `JinaRerankRequest` with string documents, so the generic
|
|
110
|
+
* registration forwards directly.
|
|
111
|
+
*/
|
|
112
|
+
const layer = (cfg) => {
|
|
113
|
+
const typed = Layer.effect(JinaReranker, make(cfg));
|
|
114
|
+
const generic = Layer.effect(Reranker, Effect.map(make(cfg), (s) => ({ rerank: (request) => s.rerank(request) })));
|
|
115
|
+
return Layer.merge(typed, generic);
|
|
116
|
+
};
|
|
117
|
+
//#endregion
|
|
118
|
+
export { JinaReranker, layer, make, JinaReranker_exports as t };
|
|
119
|
+
|
|
120
|
+
//# sourceMappingURL=JinaReranker.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"JinaReranker.mjs","names":[],"sources":["../src/JinaReranker.ts"],"sourcesContent":["import { Context, Effect, Encoding, Layer, Match, type Redacted, Schema } from \"effect\"\nimport { HttpClient, HttpClientRequest } from \"effect/unstable/http\"\nimport * as AiError from \"@effect-uai/core/AiError\"\nimport type { ImageSource } from \"@effect-uai/core/Image\"\nimport {\n type CommonRerankRequest,\n Reranker,\n type RerankerService,\n type RerankResponse,\n type RerankUsage,\n} from \"@effect-uai/core/Reranker\"\nimport type { JinaRerankerModel } from \"./models.js\"\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/**\n * A candidate document. Text models take strings; `jina-reranker-m0` also\n * takes `{ text }` / `{ image }`. One entry is one modality, and image\n * *queries* are local-inference only, so `query` stays a string.\n */\nexport type JinaRerankDocument =\n | string\n | { readonly text: string }\n | { readonly image: ImageSource }\n\n/** Widens {@link CommonRerankRequest} with Jina's multimodal documents. */\nexport type JinaRerankRequest = Omit<CommonRerankRequest, \"model\" | \"documents\"> & {\n readonly model: JinaRerankerModel\n readonly documents: ReadonlyArray<JinaRerankDocument>\n}\n\nexport type JinaRerankerService = {\n readonly rerank: (request: JinaRerankRequest) => Effect.Effect<RerankResponse, AiError.AiError>\n}\n\n/**\n * Provider-typed service tag. Yield this for multimodal documents; yield the\n * generic `Reranker` tag for provider-portable code. Both are registered by\n * {@link layer}.\n */\nexport class JinaReranker extends Context.Service<JinaReranker, JinaRerankerService>()(\n \"@betalyra/effect-uai/providers/jina/JinaReranker\",\n) {}\n\nexport type Config = {\n readonly apiKey: Redacted.Redacted\n readonly baseUrl?: string\n}\n\n// ---------------------------------------------------------------------------\n// Codec - request body\n// ---------------------------------------------------------------------------\n\ntype WireDocument = string | { readonly text: string } | { readonly image: string }\n\n// Jina detects URL vs base64 from the value itself, so both go in `image`.\nconst imageSourceToWire: (s: ImageSource) => WireDocument = Match.type<ImageSource>().pipe(\n Match.tag(\"url\", (s): WireDocument => ({ image: s.url })),\n Match.tag(\"base64\", (s): WireDocument => ({ image: s.base64 })),\n Match.tag(\"bytes\", (s): WireDocument => ({ image: Encoding.encodeBase64(s.bytes) })),\n Match.exhaustive,\n)\n\nconst documentToWire: (doc: JinaRerankDocument) => WireDocument =\n Match.type<JinaRerankDocument>().pipe(\n Match.when(Match.string, (s): WireDocument => s),\n Match.when({ text: Match.string }, ({ text }): WireDocument => ({ text })),\n Match.when({ image: Match.any }, ({ image }) => imageSourceToWire(image)),\n Match.exhaustive,\n )\n\ntype WireBody = {\n readonly model: string\n readonly query: string\n readonly documents: ReadonlyArray<WireDocument>\n readonly top_n?: number\n}\n\n// No `return_documents`: the caller holds `documents` and `results[].index`\n// points back into it.\nconst buildBody = (request: JinaRerankRequest): WireBody => ({\n model: request.model,\n query: request.query,\n documents: request.documents.map(documentToWire),\n ...(request.topN !== undefined && { top_n: request.topN }),\n})\n\n// ---------------------------------------------------------------------------\n// Codec - response\n// ---------------------------------------------------------------------------\n\nconst WireResult = Schema.Struct({\n index: Schema.Number,\n relevance_score: Schema.Number,\n})\n\nconst WireUsage = Schema.Struct({\n total_tokens: Schema.optional(Schema.Number),\n})\ntype WireUsage = typeof WireUsage.Type\n\nconst WireResponse = Schema.Struct({\n model: Schema.optional(Schema.String),\n usage: Schema.optional(WireUsage),\n results: Schema.Array(WireResult),\n})\ntype WireResponse = typeof WireResponse.Type\n\nconst usageOf = (usage: WireUsage | undefined): RerankUsage =>\n usage?.total_tokens !== undefined ? { totalTokens: usage.total_tokens } : {}\n\n// Sort rather than trust wire order: the score contract promises descending.\nconst toResponse = (wire: WireResponse): RerankResponse => ({\n results: [...wire.results]\n .sort((a, b) => b.relevance_score - a.relevance_score)\n .map((r) => ({ index: r.index, score: r.relevance_score })),\n usage: usageOf(wire.usage),\n})\n\n// ---------------------------------------------------------------------------\n// HTTP\n// ---------------------------------------------------------------------------\n\nconst transportFailure = (cause: unknown): AiError.AiError =>\n new AiError.Unavailable({ provider: \"jina\", raw: cause })\n\nconst httpStatusError = (status: number, body: string): AiError.AiError => {\n const provider = \"jina\"\n const raw = body\n if (status === 429) return new AiError.RateLimited({ provider, raw })\n if (status === 408 || status === 504) return new AiError.Timeout({ provider, raw })\n if (status === 401) return new AiError.AuthFailed({ provider, subtype: \"auth\", raw })\n if (status === 403) return new AiError.AuthFailed({ provider, subtype: \"permission\", raw })\n if (status === 402) return new AiError.AuthFailed({ provider, subtype: \"billing\", raw })\n if (status === 413) return new AiError.ContextLengthExceeded({ provider, raw })\n if (status >= 500) return new AiError.Unavailable({ provider, status, raw })\n return new AiError.InvalidRequest({ provider, raw })\n}\n\nconst baseUrl = (cfg: Config): string => cfg.baseUrl ?? \"https://api.jina.ai/v1\"\n\nconst postRerank = (\n cfg: Config,\n body: WireBody,\n): Effect.Effect<WireResponse, AiError.AiError, HttpClient.HttpClient> =>\n Effect.gen(function* () {\n const client = yield* HttpClient.HttpClient\n const httpRequest = HttpClientRequest.post(`${baseUrl(cfg)}/rerank`).pipe(\n HttpClientRequest.bearerToken(cfg.apiKey),\n HttpClientRequest.bodyJsonUnsafe(body),\n )\n const response = yield* client.execute(httpRequest).pipe(Effect.mapError(transportFailure))\n if (response.status >= 400) {\n const text = yield* response.text.pipe(Effect.orElseSucceed(() => \"\"))\n return yield* httpStatusError(response.status, text)\n }\n const json = yield* response.json.pipe(Effect.mapError(transportFailure))\n return yield* Schema.decodeUnknownEffect(WireResponse)(json).pipe(\n Effect.mapError(transportFailure),\n )\n })\n\nconst rerankImpl =\n (cfg: Config) =>\n (\n request: JinaRerankRequest,\n ): Effect.Effect<RerankResponse, AiError.AiError, HttpClient.HttpClient> =>\n Effect.map(postRerank(cfg, buildBody(request)), toResponse)\n\n// ---------------------------------------------------------------------------\n// Constructors\n// ---------------------------------------------------------------------------\n\n/**\n * Build a `JinaRerankerService` value. For Layer-based setup, prefer\n * {@link layer}.\n */\nexport const make = (\n cfg: Config,\n): Effect.Effect<JinaRerankerService, never, HttpClient.HttpClient> =>\n Effect.map(HttpClient.HttpClient, (client) => ({\n rerank: (request) =>\n rerankImpl(cfg)(request).pipe(Effect.provideService(HttpClient.HttpClient, client)),\n }))\n\n/**\n * Layer registering both the provider-typed `JinaReranker` tag and the generic\n * `Reranker` tag over one implementation. A `CommonRerankRequest` is\n * structurally a `JinaRerankRequest` with string documents, so the generic\n * registration forwards directly.\n */\nexport const layer = (\n cfg: Config,\n): Layer.Layer<JinaReranker | Reranker, never, HttpClient.HttpClient> => {\n const typed = Layer.effect(JinaReranker, make(cfg))\n const generic = Layer.effect(\n Reranker,\n Effect.map(make(cfg), (s): RerankerService => ({ rerank: (request) => s.rerank(request) })),\n )\n return Layer.merge(typed, generic)\n}\n"],"mappings":"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"}
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { JinaEmbeddingModel, JinaEngine } from "./models.mjs";
|
|
1
|
+
import { JinaEmbeddingModel, JinaEngine, JinaRerankerModel } from "./models.mjs";
|
|
2
2
|
import { t as JinaEmbedding_d_exports } from "./JinaEmbedding.mjs";
|
|
3
3
|
import { t as JinaReader_d_exports } from "./JinaReader.mjs";
|
|
4
|
-
|
|
4
|
+
import { t as JinaReranker_d_exports } from "./JinaReranker.mjs";
|
|
5
|
+
export { JinaEmbedding_d_exports as JinaEmbedding, JinaEmbeddingModel, JinaEngine, JinaReader_d_exports as JinaReader, JinaReranker_d_exports as JinaReranker, JinaRerankerModel };
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { t as JinaEmbedding_exports } from "./JinaEmbedding.mjs";
|
|
2
2
|
import { t as JinaReader_exports } from "./JinaReader.mjs";
|
|
3
|
+
import { t as JinaReranker_exports } from "./JinaReranker.mjs";
|
|
3
4
|
import "./models.mjs";
|
|
4
|
-
export { JinaEmbedding_exports as JinaEmbedding, JinaReader_exports as JinaReader };
|
|
5
|
+
export { JinaEmbedding_exports as JinaEmbedding, JinaReader_exports as JinaReader, JinaReranker_exports as JinaReranker };
|
package/dist/models.d.mts
CHANGED
|
@@ -19,6 +19,17 @@
|
|
|
19
19
|
* Reference: https://jina.ai/embeddings/
|
|
20
20
|
*/
|
|
21
21
|
type JinaEmbeddingModel = "jina-embeddings-v4" | "jina-embeddings-v5-text-small" | "jina-embeddings-v5-text-nano" | "jina-embeddings-v3" | "jina-clip-v2" | (string & {});
|
|
22
|
+
/**
|
|
23
|
+
* Known Jina reranker model identifiers.
|
|
24
|
+
*
|
|
25
|
+
* - `jina-reranker-v3.5`: flagship listwise reranker, 131k context.
|
|
26
|
+
* - `jina-reranker-v3`: previous generation, schema-identical.
|
|
27
|
+
* - `jina-reranker-m0`: multimodal, takes `{ text }` / `{ image }`
|
|
28
|
+
* documents (see `JinaRerankRequest`).
|
|
29
|
+
*
|
|
30
|
+
* Reference: https://jina.ai/reranker/
|
|
31
|
+
*/
|
|
32
|
+
type JinaRerankerModel = "jina-reranker-v3.5" | "jina-reranker-v3" | "jina-reranker-m0" | (string & {});
|
|
22
33
|
/**
|
|
23
34
|
* Reader fetching engine (`X-Engine`). `browser` renders JavaScript,
|
|
24
35
|
* `curl` is a fast static fetch, `auto` picks per page. Provider-specific,
|
|
@@ -26,5 +37,5 @@ type JinaEmbeddingModel = "jina-embeddings-v4" | "jina-embeddings-v5-text-small"
|
|
|
26
37
|
*/
|
|
27
38
|
type JinaEngine = "browser" | "curl" | "auto" | (string & {});
|
|
28
39
|
//#endregion
|
|
29
|
-
export { JinaEmbeddingModel, JinaEngine };
|
|
40
|
+
export { JinaEmbeddingModel, JinaEngine, JinaRerankerModel };
|
|
30
41
|
//# sourceMappingURL=models.d.mts.map
|
package/dist/models.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"models.d.mts","names":[],"sources":["../src/models.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;KAmBY;;;;;;
|
|
1
|
+
{"version":3,"file":"models.d.mts","names":[],"sources":["../src/models.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;KAmBY;;;;;;;;;;;KAmBA;;;;;;KAYA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@effect-uai/jina",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "Jina AI provider for @effect-uai/core (embeddings, rerank, web read).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -42,6 +42,10 @@
|
|
|
42
42
|
"./JinaReader": {
|
|
43
43
|
"types": "./dist/JinaReader.d.mts",
|
|
44
44
|
"import": "./dist/JinaReader.mjs"
|
|
45
|
+
},
|
|
46
|
+
"./JinaReranker": {
|
|
47
|
+
"types": "./dist/JinaReranker.d.mts",
|
|
48
|
+
"import": "./dist/JinaReranker.mjs"
|
|
45
49
|
}
|
|
46
50
|
},
|
|
47
51
|
"publishConfig": {
|
|
@@ -50,7 +54,7 @@
|
|
|
50
54
|
"devDependencies": {
|
|
51
55
|
"effect": "4.0.0-rc.111",
|
|
52
56
|
"typescript": "^7.0.2",
|
|
53
|
-
"@effect-uai/core": "0.
|
|
57
|
+
"@effect-uai/core": "0.14.0"
|
|
54
58
|
},
|
|
55
59
|
"peerDependencies": {
|
|
56
60
|
"@effect-uai/core": ">=0.2.0 <1",
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { Context, Effect, Encoding, Layer, Match, type Redacted, Schema } from "effect"
|
|
2
|
+
import { HttpClient, HttpClientRequest } from "effect/unstable/http"
|
|
3
|
+
import * as AiError from "@effect-uai/core/AiError"
|
|
4
|
+
import type { ImageSource } from "@effect-uai/core/Image"
|
|
5
|
+
import {
|
|
6
|
+
type CommonRerankRequest,
|
|
7
|
+
Reranker,
|
|
8
|
+
type RerankerService,
|
|
9
|
+
type RerankResponse,
|
|
10
|
+
type RerankUsage,
|
|
11
|
+
} from "@effect-uai/core/Reranker"
|
|
12
|
+
import type { JinaRerankerModel } from "./models.js"
|
|
13
|
+
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Public types
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* A candidate document. Text models take strings; `jina-reranker-m0` also
|
|
20
|
+
* takes `{ text }` / `{ image }`. One entry is one modality, and image
|
|
21
|
+
* *queries* are local-inference only, so `query` stays a string.
|
|
22
|
+
*/
|
|
23
|
+
export type JinaRerankDocument =
|
|
24
|
+
| string
|
|
25
|
+
| { readonly text: string }
|
|
26
|
+
| { readonly image: ImageSource }
|
|
27
|
+
|
|
28
|
+
/** Widens {@link CommonRerankRequest} with Jina's multimodal documents. */
|
|
29
|
+
export type JinaRerankRequest = Omit<CommonRerankRequest, "model" | "documents"> & {
|
|
30
|
+
readonly model: JinaRerankerModel
|
|
31
|
+
readonly documents: ReadonlyArray<JinaRerankDocument>
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export type JinaRerankerService = {
|
|
35
|
+
readonly rerank: (request: JinaRerankRequest) => Effect.Effect<RerankResponse, AiError.AiError>
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Provider-typed service tag. Yield this for multimodal documents; yield the
|
|
40
|
+
* generic `Reranker` tag for provider-portable code. Both are registered by
|
|
41
|
+
* {@link layer}.
|
|
42
|
+
*/
|
|
43
|
+
export class JinaReranker extends Context.Service<JinaReranker, JinaRerankerService>()(
|
|
44
|
+
"@betalyra/effect-uai/providers/jina/JinaReranker",
|
|
45
|
+
) {}
|
|
46
|
+
|
|
47
|
+
export type Config = {
|
|
48
|
+
readonly apiKey: Redacted.Redacted
|
|
49
|
+
readonly baseUrl?: string
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
// Codec - request body
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
|
|
56
|
+
type WireDocument = string | { readonly text: string } | { readonly image: string }
|
|
57
|
+
|
|
58
|
+
// Jina detects URL vs base64 from the value itself, so both go in `image`.
|
|
59
|
+
const imageSourceToWire: (s: ImageSource) => WireDocument = Match.type<ImageSource>().pipe(
|
|
60
|
+
Match.tag("url", (s): WireDocument => ({ image: s.url })),
|
|
61
|
+
Match.tag("base64", (s): WireDocument => ({ image: s.base64 })),
|
|
62
|
+
Match.tag("bytes", (s): WireDocument => ({ image: Encoding.encodeBase64(s.bytes) })),
|
|
63
|
+
Match.exhaustive,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
const documentToWire: (doc: JinaRerankDocument) => WireDocument =
|
|
67
|
+
Match.type<JinaRerankDocument>().pipe(
|
|
68
|
+
Match.when(Match.string, (s): WireDocument => s),
|
|
69
|
+
Match.when({ text: Match.string }, ({ text }): WireDocument => ({ text })),
|
|
70
|
+
Match.when({ image: Match.any }, ({ image }) => imageSourceToWire(image)),
|
|
71
|
+
Match.exhaustive,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
type WireBody = {
|
|
75
|
+
readonly model: string
|
|
76
|
+
readonly query: string
|
|
77
|
+
readonly documents: ReadonlyArray<WireDocument>
|
|
78
|
+
readonly top_n?: number
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// No `return_documents`: the caller holds `documents` and `results[].index`
|
|
82
|
+
// points back into it.
|
|
83
|
+
const buildBody = (request: JinaRerankRequest): WireBody => ({
|
|
84
|
+
model: request.model,
|
|
85
|
+
query: request.query,
|
|
86
|
+
documents: request.documents.map(documentToWire),
|
|
87
|
+
...(request.topN !== undefined && { top_n: request.topN }),
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
// Codec - response
|
|
92
|
+
// ---------------------------------------------------------------------------
|
|
93
|
+
|
|
94
|
+
const WireResult = Schema.Struct({
|
|
95
|
+
index: Schema.Number,
|
|
96
|
+
relevance_score: Schema.Number,
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
const WireUsage = Schema.Struct({
|
|
100
|
+
total_tokens: Schema.optional(Schema.Number),
|
|
101
|
+
})
|
|
102
|
+
type WireUsage = typeof WireUsage.Type
|
|
103
|
+
|
|
104
|
+
const WireResponse = Schema.Struct({
|
|
105
|
+
model: Schema.optional(Schema.String),
|
|
106
|
+
usage: Schema.optional(WireUsage),
|
|
107
|
+
results: Schema.Array(WireResult),
|
|
108
|
+
})
|
|
109
|
+
type WireResponse = typeof WireResponse.Type
|
|
110
|
+
|
|
111
|
+
const usageOf = (usage: WireUsage | undefined): RerankUsage =>
|
|
112
|
+
usage?.total_tokens !== undefined ? { totalTokens: usage.total_tokens } : {}
|
|
113
|
+
|
|
114
|
+
// Sort rather than trust wire order: the score contract promises descending.
|
|
115
|
+
const toResponse = (wire: WireResponse): RerankResponse => ({
|
|
116
|
+
results: [...wire.results]
|
|
117
|
+
.sort((a, b) => b.relevance_score - a.relevance_score)
|
|
118
|
+
.map((r) => ({ index: r.index, score: r.relevance_score })),
|
|
119
|
+
usage: usageOf(wire.usage),
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
// ---------------------------------------------------------------------------
|
|
123
|
+
// HTTP
|
|
124
|
+
// ---------------------------------------------------------------------------
|
|
125
|
+
|
|
126
|
+
const transportFailure = (cause: unknown): AiError.AiError =>
|
|
127
|
+
new AiError.Unavailable({ provider: "jina", raw: cause })
|
|
128
|
+
|
|
129
|
+
const httpStatusError = (status: number, body: string): AiError.AiError => {
|
|
130
|
+
const provider = "jina"
|
|
131
|
+
const raw = body
|
|
132
|
+
if (status === 429) return new AiError.RateLimited({ provider, raw })
|
|
133
|
+
if (status === 408 || status === 504) return new AiError.Timeout({ provider, raw })
|
|
134
|
+
if (status === 401) return new AiError.AuthFailed({ provider, subtype: "auth", raw })
|
|
135
|
+
if (status === 403) return new AiError.AuthFailed({ provider, subtype: "permission", raw })
|
|
136
|
+
if (status === 402) return new AiError.AuthFailed({ provider, subtype: "billing", raw })
|
|
137
|
+
if (status === 413) return new AiError.ContextLengthExceeded({ provider, raw })
|
|
138
|
+
if (status >= 500) return new AiError.Unavailable({ provider, status, raw })
|
|
139
|
+
return new AiError.InvalidRequest({ provider, raw })
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const baseUrl = (cfg: Config): string => cfg.baseUrl ?? "https://api.jina.ai/v1"
|
|
143
|
+
|
|
144
|
+
const postRerank = (
|
|
145
|
+
cfg: Config,
|
|
146
|
+
body: WireBody,
|
|
147
|
+
): Effect.Effect<WireResponse, AiError.AiError, HttpClient.HttpClient> =>
|
|
148
|
+
Effect.gen(function* () {
|
|
149
|
+
const client = yield* HttpClient.HttpClient
|
|
150
|
+
const httpRequest = HttpClientRequest.post(`${baseUrl(cfg)}/rerank`).pipe(
|
|
151
|
+
HttpClientRequest.bearerToken(cfg.apiKey),
|
|
152
|
+
HttpClientRequest.bodyJsonUnsafe(body),
|
|
153
|
+
)
|
|
154
|
+
const response = yield* client.execute(httpRequest).pipe(Effect.mapError(transportFailure))
|
|
155
|
+
if (response.status >= 400) {
|
|
156
|
+
const text = yield* response.text.pipe(Effect.orElseSucceed(() => ""))
|
|
157
|
+
return yield* httpStatusError(response.status, text)
|
|
158
|
+
}
|
|
159
|
+
const json = yield* response.json.pipe(Effect.mapError(transportFailure))
|
|
160
|
+
return yield* Schema.decodeUnknownEffect(WireResponse)(json).pipe(
|
|
161
|
+
Effect.mapError(transportFailure),
|
|
162
|
+
)
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
const rerankImpl =
|
|
166
|
+
(cfg: Config) =>
|
|
167
|
+
(
|
|
168
|
+
request: JinaRerankRequest,
|
|
169
|
+
): Effect.Effect<RerankResponse, AiError.AiError, HttpClient.HttpClient> =>
|
|
170
|
+
Effect.map(postRerank(cfg, buildBody(request)), toResponse)
|
|
171
|
+
|
|
172
|
+
// ---------------------------------------------------------------------------
|
|
173
|
+
// Constructors
|
|
174
|
+
// ---------------------------------------------------------------------------
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Build a `JinaRerankerService` value. For Layer-based setup, prefer
|
|
178
|
+
* {@link layer}.
|
|
179
|
+
*/
|
|
180
|
+
export const make = (
|
|
181
|
+
cfg: Config,
|
|
182
|
+
): Effect.Effect<JinaRerankerService, never, HttpClient.HttpClient> =>
|
|
183
|
+
Effect.map(HttpClient.HttpClient, (client) => ({
|
|
184
|
+
rerank: (request) =>
|
|
185
|
+
rerankImpl(cfg)(request).pipe(Effect.provideService(HttpClient.HttpClient, client)),
|
|
186
|
+
}))
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Layer registering both the provider-typed `JinaReranker` tag and the generic
|
|
190
|
+
* `Reranker` tag over one implementation. A `CommonRerankRequest` is
|
|
191
|
+
* structurally a `JinaRerankRequest` with string documents, so the generic
|
|
192
|
+
* registration forwards directly.
|
|
193
|
+
*/
|
|
194
|
+
export const layer = (
|
|
195
|
+
cfg: Config,
|
|
196
|
+
): Layer.Layer<JinaReranker | Reranker, never, HttpClient.HttpClient> => {
|
|
197
|
+
const typed = Layer.effect(JinaReranker, make(cfg))
|
|
198
|
+
const generic = Layer.effect(
|
|
199
|
+
Reranker,
|
|
200
|
+
Effect.map(make(cfg), (s): RerankerService => ({ rerank: (request) => s.rerank(request) })),
|
|
201
|
+
)
|
|
202
|
+
return Layer.merge(typed, generic)
|
|
203
|
+
}
|
package/src/index.ts
CHANGED
package/src/models.ts
CHANGED
|
@@ -26,6 +26,23 @@ export type JinaEmbeddingModel =
|
|
|
26
26
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
27
27
|
| (string & {})
|
|
28
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Known Jina reranker model identifiers.
|
|
31
|
+
*
|
|
32
|
+
* - `jina-reranker-v3.5`: flagship listwise reranker, 131k context.
|
|
33
|
+
* - `jina-reranker-v3`: previous generation, schema-identical.
|
|
34
|
+
* - `jina-reranker-m0`: multimodal, takes `{ text }` / `{ image }`
|
|
35
|
+
* documents (see `JinaRerankRequest`).
|
|
36
|
+
*
|
|
37
|
+
* Reference: https://jina.ai/reranker/
|
|
38
|
+
*/
|
|
39
|
+
export type JinaRerankerModel =
|
|
40
|
+
| "jina-reranker-v3.5"
|
|
41
|
+
| "jina-reranker-v3"
|
|
42
|
+
| "jina-reranker-m0"
|
|
43
|
+
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
44
|
+
| (string & {})
|
|
45
|
+
|
|
29
46
|
/**
|
|
30
47
|
* Reader fetching engine (`X-Engine`). `browser` renders JavaScript,
|
|
31
48
|
* `curl` is a fast static fetch, `auto` picks per page. Provider-specific,
|