@textopt/langchain 0.1.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/LICENSE +21 -0
- package/README.md +73 -0
- package/dist/index.cjs +311 -0
- package/dist/index.d.cts +74 -0
- package/dist/index.d.mts +74 -0
- package/dist/index.mjs +310 -0
- package/package.json +61 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Charlie Duong
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# @textopt/langchain
|
|
2
|
+
|
|
3
|
+
LangChain adapter for [textopt](https://github.com/ctdio/textopt#readme).
|
|
4
|
+
|
|
5
|
+
The adapter rebuilds a LangChain runnable, chain, agent, or LangGraph graph for each candidate. Candidate components can supply prompts, tool descriptions, routing instructions, or other text read by the runnable.
|
|
6
|
+
|
|
7
|
+
`@langchain/core` is a peer dependency (`>=0.3.0 <2`).
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { GepaOptimizer } from "textopt/gepa";
|
|
13
|
+
import { createLangChainAdapter } from "@textopt/langchain";
|
|
14
|
+
import { ChatPromptTemplate } from "@langchain/core/prompts";
|
|
15
|
+
import { StringOutputParser } from "@langchain/core/output_parsers";
|
|
16
|
+
|
|
17
|
+
const adapter = createLangChainAdapter<Ticket, string>({
|
|
18
|
+
buildRunnable: (candidate) =>
|
|
19
|
+
ChatPromptTemplate.fromMessages([
|
|
20
|
+
["system", candidate.system ?? ""],
|
|
21
|
+
["human", "{text}"],
|
|
22
|
+
])
|
|
23
|
+
.pipe(model)
|
|
24
|
+
.pipe(new StringOutputParser()),
|
|
25
|
+
|
|
26
|
+
toInput: (datum) => ({ text: datum.text }),
|
|
27
|
+
|
|
28
|
+
score: ({ datum, output }) =>
|
|
29
|
+
output === datum.label
|
|
30
|
+
? { score: 1, feedback: `Correct: ${datum.label}.` }
|
|
31
|
+
: {
|
|
32
|
+
score: 0,
|
|
33
|
+
feedback: `Predicted "${output}", expected "${datum.label}".`,
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const result = await new GepaOptimizer().optimize({
|
|
38
|
+
seedCandidate: { system: "Classify the support ticket." },
|
|
39
|
+
trainingSet,
|
|
40
|
+
adapter,
|
|
41
|
+
reflect,
|
|
42
|
+
maxMetricCalls: 150,
|
|
43
|
+
});
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
`score` returns a `ScoreResult`. GEPA uses its optional `feedback` field during reflection, so include a description of the failure when possible.
|
|
47
|
+
|
|
48
|
+
## Options
|
|
49
|
+
|
|
50
|
+
| Option | Default | Effect |
|
|
51
|
+
| ------------------- | ------------------------- | ------------------------------------------------------------------------------------------ |
|
|
52
|
+
| `buildRunnable` | required | Rebuilds the chain with the candidate's text injected into its prompts. |
|
|
53
|
+
| `score` | required | Scores one rollout, given the `datum`, `output`, and `trace`. |
|
|
54
|
+
| `toInput` | the row itself | Maps a dataset row to the chain's input. |
|
|
55
|
+
| `concurrency` | `8` | In-flight rollouts. |
|
|
56
|
+
| `includeChainSteps` | `false` | Includes per-runnable chain spans in traces. |
|
|
57
|
+
| `componentRunNames` | none | Maps component names to run names and filters each component's evidence to matching steps. |
|
|
58
|
+
| `isTransient` | `false` for all errors | Identifies infrastructure errors whose fallback scores must not be cached. |
|
|
59
|
+
| `buildRecord` | default reflective record | Builds a custom reflective record. |
|
|
60
|
+
|
|
61
|
+
## Tracing
|
|
62
|
+
|
|
63
|
+
Each rollout includes `textopt_iteration`, `textopt_phase`, `textopt_split`, and `textopt_candidate_id` in its LangChain metadata. With a tracer configured, these fields can filter and group runs in LangSmith.
|
|
64
|
+
|
|
65
|
+
Traces include LLM, tool, and retriever spans with run IDs, parent run IDs, inputs, outputs, and errors. Set `includeChainSteps` to include chain spans.
|
|
66
|
+
|
|
67
|
+
Tracing callbacks are attached only when the optimizer requests traces, typically for reflection minibatches. During validation sweeps, `score` receives a trace with no steps and should score the output directly.
|
|
68
|
+
|
|
69
|
+
Types: `LangChainAdapterOptions`, `LangChainTrace`, `LangChainTraceStep`, `LangChainStepType`, `LangChainEvidence`, `LangChainScore`.
|
|
70
|
+
|
|
71
|
+
## License
|
|
72
|
+
|
|
73
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
let textopt = require("textopt");
|
|
3
|
+
//#region src/adapter.ts
|
|
4
|
+
const DEFAULT_CONCURRENCY = 8;
|
|
5
|
+
/**
|
|
6
|
+
* Runs a LangChain runnable (chain, agent, or LangGraph graph) as the system
|
|
7
|
+
* under optimization. The candidate is injected by rebuilding the runnable, so
|
|
8
|
+
* anything that reads candidate text — prompts, tool descriptions, routing
|
|
9
|
+
* instructions — is optimizable.
|
|
10
|
+
*/
|
|
11
|
+
function createLangChainAdapter(options) {
|
|
12
|
+
const { buildRunnable, score, toInput = (datum) => datum, concurrency = DEFAULT_CONCURRENCY, includeChainSteps = false, pricing, componentRunNames, isTransient = () => false, buildRecord } = options;
|
|
13
|
+
return {
|
|
14
|
+
evaluate: async ({ batch, candidate, captureTraces, run, signal }) => {
|
|
15
|
+
const runnable = buildRunnable(candidate);
|
|
16
|
+
const metadata = {
|
|
17
|
+
textopt_iteration: run.iteration,
|
|
18
|
+
textopt_phase: run.phase,
|
|
19
|
+
textopt_split: run.split,
|
|
20
|
+
textopt_candidate_id: run.candidateId
|
|
21
|
+
};
|
|
22
|
+
const results = await (0, textopt.mapWithConcurrency)({
|
|
23
|
+
items: batch,
|
|
24
|
+
limit: concurrency,
|
|
25
|
+
task: async (datum) => {
|
|
26
|
+
const collector = createTraceCollector({ includeChainSteps });
|
|
27
|
+
const startedAt = Date.now();
|
|
28
|
+
let output = null;
|
|
29
|
+
let failure;
|
|
30
|
+
let transient = false;
|
|
31
|
+
try {
|
|
32
|
+
output = await runnable.invoke(toInput(datum), {
|
|
33
|
+
callbacks: [collector.handler],
|
|
34
|
+
metadata,
|
|
35
|
+
signal
|
|
36
|
+
});
|
|
37
|
+
} catch (err) {
|
|
38
|
+
signal?.throwIfAborted();
|
|
39
|
+
failure = err instanceof Error ? err.message : String(err);
|
|
40
|
+
transient = isTransient(err);
|
|
41
|
+
}
|
|
42
|
+
const steps = collector.finish();
|
|
43
|
+
const usage = (0, textopt.priceUsage)({
|
|
44
|
+
usage: collector.usage(),
|
|
45
|
+
...pricing === void 0 ? {} : { pricing }
|
|
46
|
+
});
|
|
47
|
+
const trace = {
|
|
48
|
+
steps: captureTraces ? steps : [],
|
|
49
|
+
durationMs: Date.now() - startedAt,
|
|
50
|
+
...failure === void 0 ? {} : { error: failure }
|
|
51
|
+
};
|
|
52
|
+
if (failure !== void 0) return {
|
|
53
|
+
output: null,
|
|
54
|
+
trace,
|
|
55
|
+
usage,
|
|
56
|
+
scored: {
|
|
57
|
+
score: 0,
|
|
58
|
+
feedback: `Run failed: ${failure}`,
|
|
59
|
+
transient
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
try {
|
|
63
|
+
return {
|
|
64
|
+
output,
|
|
65
|
+
trace,
|
|
66
|
+
usage,
|
|
67
|
+
scored: await score({
|
|
68
|
+
datum,
|
|
69
|
+
output,
|
|
70
|
+
trace
|
|
71
|
+
})
|
|
72
|
+
};
|
|
73
|
+
} catch (err) {
|
|
74
|
+
signal?.throwIfAborted();
|
|
75
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
76
|
+
return {
|
|
77
|
+
output,
|
|
78
|
+
trace,
|
|
79
|
+
usage,
|
|
80
|
+
scored: {
|
|
81
|
+
score: 0,
|
|
82
|
+
feedback: `Scoring failed: ${message}`,
|
|
83
|
+
transient: isTransient(err)
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
signal
|
|
89
|
+
});
|
|
90
|
+
const evaluation = {
|
|
91
|
+
outputs: results.map((result) => result.output),
|
|
92
|
+
scores: results.map((result) => result.scored.score),
|
|
93
|
+
feedback: results.map((result) => result.scored.feedback ?? "")
|
|
94
|
+
};
|
|
95
|
+
if (results.some((result) => result.scored.usage !== void 0 || Object.keys(result.usage).length > 0)) evaluation.usage = results.map((result) => result.scored.usage ?? result.usage);
|
|
96
|
+
if (captureTraces) evaluation.trajectories = results.map((result) => result.trace);
|
|
97
|
+
if (results.some((result) => result.scored.objectiveScores !== void 0)) evaluation.objectiveScores = results.map((result) => result.scored.objectiveScores ?? {});
|
|
98
|
+
if (results.some((result) => result.scored.transient === true)) evaluation.transient = results.map((result) => result.scored.transient === true);
|
|
99
|
+
return evaluation;
|
|
100
|
+
},
|
|
101
|
+
makeReflectiveDataset: ({ batch, evaluation, componentsToUpdate }) => {
|
|
102
|
+
const dataset = {};
|
|
103
|
+
for (const component of componentsToUpdate) dataset[component] = batch.map((datum, index) => {
|
|
104
|
+
const trace = evaluation.trajectories?.[index] ?? {
|
|
105
|
+
steps: [],
|
|
106
|
+
durationMs: 0
|
|
107
|
+
};
|
|
108
|
+
const output = evaluation.outputs[index] ?? null;
|
|
109
|
+
const scoreValue = evaluation.scores[index] ?? 0;
|
|
110
|
+
const feedback = evaluation.feedback?.[index] ?? "";
|
|
111
|
+
if (buildRecord !== void 0) return buildRecord({
|
|
112
|
+
datum,
|
|
113
|
+
output,
|
|
114
|
+
trace,
|
|
115
|
+
score: scoreValue,
|
|
116
|
+
feedback,
|
|
117
|
+
component
|
|
118
|
+
});
|
|
119
|
+
const componentSteps = selectComponentSteps({
|
|
120
|
+
trace,
|
|
121
|
+
runName: componentRunNames?.[component]
|
|
122
|
+
});
|
|
123
|
+
return {
|
|
124
|
+
inputs: toInput(datum),
|
|
125
|
+
generatedOutputs: output,
|
|
126
|
+
feedback,
|
|
127
|
+
score: scoreValue,
|
|
128
|
+
...componentSteps.length > 0 ? { evidence: { trace: componentSteps } } : {}
|
|
129
|
+
};
|
|
130
|
+
});
|
|
131
|
+
return dataset;
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
function selectComponentSteps(args) {
|
|
136
|
+
const { trace, runName } = args;
|
|
137
|
+
if (runName === void 0) return trace.steps;
|
|
138
|
+
return trace.steps.filter((step) => step.name === runName);
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* LangChain accepts plain handler objects, so trace capture needs no subclass
|
|
142
|
+
* and no LangSmith account — the same events LangSmith records land here.
|
|
143
|
+
*/
|
|
144
|
+
function createTraceCollector(args) {
|
|
145
|
+
const { includeChainSteps } = args;
|
|
146
|
+
const steps = [];
|
|
147
|
+
const openSteps = /* @__PURE__ */ new Map();
|
|
148
|
+
const tokens = {
|
|
149
|
+
inputTokens: 0,
|
|
150
|
+
outputTokens: 0,
|
|
151
|
+
totalTokens: 0
|
|
152
|
+
};
|
|
153
|
+
let counted = false;
|
|
154
|
+
function open(step) {
|
|
155
|
+
openSteps.set(step.runId, step);
|
|
156
|
+
steps.push(step);
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Any span still open when the run ends never got its end callback — the
|
|
160
|
+
* chain was torn down first. Left alone it is indistinguishable in the trace
|
|
161
|
+
* from a step that legitimately produced nothing, which quietly misleads the
|
|
162
|
+
* reflection model.
|
|
163
|
+
*/
|
|
164
|
+
function finish() {
|
|
165
|
+
for (const step of openSteps.values()) step.error = "Step did not complete before the run ended";
|
|
166
|
+
openSteps.clear();
|
|
167
|
+
return steps;
|
|
168
|
+
}
|
|
169
|
+
function close(args) {
|
|
170
|
+
const step = openSteps.get(args.runId);
|
|
171
|
+
if (step === void 0) return;
|
|
172
|
+
step.outputs = args.outputs;
|
|
173
|
+
if (args.error !== void 0) step.error = args.error;
|
|
174
|
+
openSteps.delete(args.runId);
|
|
175
|
+
}
|
|
176
|
+
const handler = {
|
|
177
|
+
handleLLMStart: (llm, prompts, runId, parentRunId, _extra, _tags, _meta, runName) => {
|
|
178
|
+
open({
|
|
179
|
+
type: "llm",
|
|
180
|
+
name: runName ?? nameOf(llm),
|
|
181
|
+
runId,
|
|
182
|
+
...parentRunId === void 0 ? {} : { parentRunId },
|
|
183
|
+
inputs: prompts
|
|
184
|
+
});
|
|
185
|
+
},
|
|
186
|
+
handleChatModelStart: (llm, messages, runId, parentRunId, _extra, _tags, _meta, runName) => {
|
|
187
|
+
open({
|
|
188
|
+
type: "llm",
|
|
189
|
+
name: runName ?? nameOf(llm),
|
|
190
|
+
runId,
|
|
191
|
+
...parentRunId === void 0 ? {} : { parentRunId },
|
|
192
|
+
inputs: messages.flat().map((message) => ({
|
|
193
|
+
role: message.getType(),
|
|
194
|
+
content: message.content
|
|
195
|
+
}))
|
|
196
|
+
});
|
|
197
|
+
},
|
|
198
|
+
handleLLMEnd: (output, runId) => {
|
|
199
|
+
countTokens(output);
|
|
200
|
+
close({
|
|
201
|
+
runId,
|
|
202
|
+
outputs: output.generations.flat().map((generation) => generation.text)
|
|
203
|
+
});
|
|
204
|
+
},
|
|
205
|
+
handleLLMError: (err, runId) => {
|
|
206
|
+
close({
|
|
207
|
+
runId,
|
|
208
|
+
outputs: null,
|
|
209
|
+
error: messageOf(err)
|
|
210
|
+
});
|
|
211
|
+
},
|
|
212
|
+
handleToolStart: (tool, input, runId, parentRunId, _tags, _meta, runName) => {
|
|
213
|
+
open({
|
|
214
|
+
type: "tool",
|
|
215
|
+
name: runName ?? nameOf(tool),
|
|
216
|
+
runId,
|
|
217
|
+
...parentRunId === void 0 ? {} : { parentRunId },
|
|
218
|
+
inputs: input
|
|
219
|
+
});
|
|
220
|
+
},
|
|
221
|
+
handleToolEnd: (output, runId) => {
|
|
222
|
+
close({
|
|
223
|
+
runId,
|
|
224
|
+
outputs: output
|
|
225
|
+
});
|
|
226
|
+
},
|
|
227
|
+
handleToolError: (err, runId) => {
|
|
228
|
+
close({
|
|
229
|
+
runId,
|
|
230
|
+
outputs: null,
|
|
231
|
+
error: messageOf(err)
|
|
232
|
+
});
|
|
233
|
+
},
|
|
234
|
+
handleRetrieverStart: (retriever, query, runId, parentRunId, _tags, _meta, runName) => {
|
|
235
|
+
open({
|
|
236
|
+
type: "retriever",
|
|
237
|
+
name: runName ?? nameOf(retriever),
|
|
238
|
+
runId,
|
|
239
|
+
...parentRunId === void 0 ? {} : { parentRunId },
|
|
240
|
+
inputs: query
|
|
241
|
+
});
|
|
242
|
+
},
|
|
243
|
+
handleRetrieverEnd: (documents, runId) => {
|
|
244
|
+
close({
|
|
245
|
+
runId,
|
|
246
|
+
outputs: documents.map((document) => document.pageContent)
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
if (includeChainSteps) {
|
|
251
|
+
handler.handleChainStart = (chain, inputs, runId, parentRunId, _tags, _meta, _runType, runName) => {
|
|
252
|
+
open({
|
|
253
|
+
type: "chain",
|
|
254
|
+
name: runName ?? nameOf(chain),
|
|
255
|
+
runId,
|
|
256
|
+
...parentRunId === void 0 ? {} : { parentRunId },
|
|
257
|
+
inputs
|
|
258
|
+
});
|
|
259
|
+
};
|
|
260
|
+
handler.handleChainEnd = (outputs, runId) => {
|
|
261
|
+
close({
|
|
262
|
+
runId,
|
|
263
|
+
outputs
|
|
264
|
+
});
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* LangChain reports tokens in two shapes depending on the integration: the
|
|
269
|
+
* legacy `llmOutput.tokenUsage` and the message-level `usage_metadata` newer
|
|
270
|
+
* chat models attach. Reading both is what makes this work across providers;
|
|
271
|
+
* an integration that fills both describes one call twice, so a message-level
|
|
272
|
+
* shape that carries a count wins and the legacy total is only a fallback.
|
|
273
|
+
*/
|
|
274
|
+
function countTokens(output) {
|
|
275
|
+
let fromMetadata = false;
|
|
276
|
+
for (const generation of output.generations?.flat() ?? []) {
|
|
277
|
+
const metadata = generation.message?.usage_metadata;
|
|
278
|
+
if (metadata === void 0) continue;
|
|
279
|
+
const inputTokens = metadata.input_tokens ?? 0;
|
|
280
|
+
const outputTokens = metadata.output_tokens ?? 0;
|
|
281
|
+
const totalTokens = metadata.total_tokens ?? inputTokens + outputTokens;
|
|
282
|
+
if (inputTokens === 0 && outputTokens === 0 && totalTokens === 0) continue;
|
|
283
|
+
fromMetadata = true;
|
|
284
|
+
counted = true;
|
|
285
|
+
tokens.inputTokens += inputTokens;
|
|
286
|
+
tokens.outputTokens += outputTokens;
|
|
287
|
+
tokens.totalTokens += totalTokens;
|
|
288
|
+
}
|
|
289
|
+
if (fromMetadata) return;
|
|
290
|
+
const legacy = output.llmOutput?.tokenUsage;
|
|
291
|
+
if (legacy !== void 0) {
|
|
292
|
+
counted = true;
|
|
293
|
+
tokens.inputTokens += legacy.promptTokens ?? 0;
|
|
294
|
+
tokens.outputTokens += legacy.completionTokens ?? 0;
|
|
295
|
+
tokens.totalTokens += legacy.totalTokens ?? (legacy.promptTokens ?? 0) + (legacy.completionTokens ?? 0);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return {
|
|
299
|
+
handler,
|
|
300
|
+
finish,
|
|
301
|
+
usage: () => counted ? { ...tokens } : {}
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
function nameOf(serialized) {
|
|
305
|
+
return serialized?.id?.at(-1) ?? "unknown";
|
|
306
|
+
}
|
|
307
|
+
function messageOf(err) {
|
|
308
|
+
return err instanceof Error ? err.message : String(err);
|
|
309
|
+
}
|
|
310
|
+
//#endregion
|
|
311
|
+
exports.createLangChainAdapter = createLangChainAdapter;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { Candidate, ScoreResult, TokenPricing } from "textopt";
|
|
2
|
+
import { GepaAdapter, ReflectiveRecord } from "textopt/gepa";
|
|
3
|
+
import { Runnable } from "@langchain/core/runnables";
|
|
4
|
+
//#region src/adapter.d.ts
|
|
5
|
+
type LangChainStepType = "llm" | "chain" | "tool" | "retriever";
|
|
6
|
+
interface LangChainTraceStep {
|
|
7
|
+
type: LangChainStepType;
|
|
8
|
+
name: string;
|
|
9
|
+
runId: string;
|
|
10
|
+
parentRunId?: string;
|
|
11
|
+
inputs?: unknown;
|
|
12
|
+
outputs?: unknown;
|
|
13
|
+
error?: string;
|
|
14
|
+
}
|
|
15
|
+
interface LangChainTrace {
|
|
16
|
+
steps: LangChainTraceStep[];
|
|
17
|
+
durationMs: number;
|
|
18
|
+
error?: string;
|
|
19
|
+
}
|
|
20
|
+
type LangChainScore = ScoreResult;
|
|
21
|
+
/** What the reflection model sees beyond the score and feedback of a run. */
|
|
22
|
+
interface LangChainEvidence {
|
|
23
|
+
trace: LangChainTraceStep[];
|
|
24
|
+
}
|
|
25
|
+
interface LangChainAdapterOptions<Datum, Output> {
|
|
26
|
+
/** Rebuild the chain with the candidate's text injected into its prompts. */
|
|
27
|
+
buildRunnable: (candidate: Candidate) => Runnable<never, Output>;
|
|
28
|
+
/**
|
|
29
|
+
* Per-instance score plus the textual feedback the reflection model reads.
|
|
30
|
+
* Feedback is what separates GEPA from blind search — say what went wrong,
|
|
31
|
+
* not just how wrong it was.
|
|
32
|
+
*/
|
|
33
|
+
score: (args: {
|
|
34
|
+
datum: Datum;
|
|
35
|
+
output: Output | null;
|
|
36
|
+
trace: LangChainTrace;
|
|
37
|
+
}) => LangChainScore | Promise<LangChainScore>;
|
|
38
|
+
/** Map a dataset row to the chain's input. Defaults to the row itself. */
|
|
39
|
+
toInput?: (datum: Datum) => unknown;
|
|
40
|
+
concurrency?: number;
|
|
41
|
+
/** Include LangChain's per-runnable chain spans in the trace. Noisy. */
|
|
42
|
+
includeChainSteps?: boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Converts the tokens the chain's model spans reported into dollars. Without
|
|
45
|
+
* it usage is still reported in tokens; with it a run can be given a spend
|
|
46
|
+
* ceiling.
|
|
47
|
+
*/
|
|
48
|
+
pricing?: TokenPricing;
|
|
49
|
+
/** Component name -> LangChain run name, used to highlight per-component IO. */
|
|
50
|
+
componentRunNames?: Record<string, string>;
|
|
51
|
+
/**
|
|
52
|
+
* Classify a thrown error as infrastructure (rate limit, 5xx, network) so
|
|
53
|
+
* its zero is not cached against the candidate. Defaults to treating every
|
|
54
|
+
* failure as the candidate's, which is the safe assumption.
|
|
55
|
+
*/
|
|
56
|
+
isTransient?: (err: unknown) => boolean;
|
|
57
|
+
buildRecord?: (args: {
|
|
58
|
+
datum: Datum;
|
|
59
|
+
output: Output | null;
|
|
60
|
+
trace: LangChainTrace;
|
|
61
|
+
score: number;
|
|
62
|
+
feedback: string;
|
|
63
|
+
component: string;
|
|
64
|
+
}) => ReflectiveRecord;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Runs a LangChain runnable (chain, agent, or LangGraph graph) as the system
|
|
68
|
+
* under optimization. The candidate is injected by rebuilding the runnable, so
|
|
69
|
+
* anything that reads candidate text — prompts, tool descriptions, routing
|
|
70
|
+
* instructions — is optimizable.
|
|
71
|
+
*/
|
|
72
|
+
declare function createLangChainAdapter<Datum, Output>(options: LangChainAdapterOptions<Datum, Output>): GepaAdapter<Datum, LangChainTrace, Output | null>;
|
|
73
|
+
//#endregion
|
|
74
|
+
export { LangChainAdapterOptions, LangChainEvidence, LangChainScore, LangChainStepType, LangChainTrace, LangChainTraceStep, createLangChainAdapter };
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { Candidate, ScoreResult, TokenPricing } from "textopt";
|
|
2
|
+
import { GepaAdapter, ReflectiveRecord } from "textopt/gepa";
|
|
3
|
+
import { Runnable } from "@langchain/core/runnables";
|
|
4
|
+
//#region src/adapter.d.ts
|
|
5
|
+
type LangChainStepType = "llm" | "chain" | "tool" | "retriever";
|
|
6
|
+
interface LangChainTraceStep {
|
|
7
|
+
type: LangChainStepType;
|
|
8
|
+
name: string;
|
|
9
|
+
runId: string;
|
|
10
|
+
parentRunId?: string;
|
|
11
|
+
inputs?: unknown;
|
|
12
|
+
outputs?: unknown;
|
|
13
|
+
error?: string;
|
|
14
|
+
}
|
|
15
|
+
interface LangChainTrace {
|
|
16
|
+
steps: LangChainTraceStep[];
|
|
17
|
+
durationMs: number;
|
|
18
|
+
error?: string;
|
|
19
|
+
}
|
|
20
|
+
type LangChainScore = ScoreResult;
|
|
21
|
+
/** What the reflection model sees beyond the score and feedback of a run. */
|
|
22
|
+
interface LangChainEvidence {
|
|
23
|
+
trace: LangChainTraceStep[];
|
|
24
|
+
}
|
|
25
|
+
interface LangChainAdapterOptions<Datum, Output> {
|
|
26
|
+
/** Rebuild the chain with the candidate's text injected into its prompts. */
|
|
27
|
+
buildRunnable: (candidate: Candidate) => Runnable<never, Output>;
|
|
28
|
+
/**
|
|
29
|
+
* Per-instance score plus the textual feedback the reflection model reads.
|
|
30
|
+
* Feedback is what separates GEPA from blind search — say what went wrong,
|
|
31
|
+
* not just how wrong it was.
|
|
32
|
+
*/
|
|
33
|
+
score: (args: {
|
|
34
|
+
datum: Datum;
|
|
35
|
+
output: Output | null;
|
|
36
|
+
trace: LangChainTrace;
|
|
37
|
+
}) => LangChainScore | Promise<LangChainScore>;
|
|
38
|
+
/** Map a dataset row to the chain's input. Defaults to the row itself. */
|
|
39
|
+
toInput?: (datum: Datum) => unknown;
|
|
40
|
+
concurrency?: number;
|
|
41
|
+
/** Include LangChain's per-runnable chain spans in the trace. Noisy. */
|
|
42
|
+
includeChainSteps?: boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Converts the tokens the chain's model spans reported into dollars. Without
|
|
45
|
+
* it usage is still reported in tokens; with it a run can be given a spend
|
|
46
|
+
* ceiling.
|
|
47
|
+
*/
|
|
48
|
+
pricing?: TokenPricing;
|
|
49
|
+
/** Component name -> LangChain run name, used to highlight per-component IO. */
|
|
50
|
+
componentRunNames?: Record<string, string>;
|
|
51
|
+
/**
|
|
52
|
+
* Classify a thrown error as infrastructure (rate limit, 5xx, network) so
|
|
53
|
+
* its zero is not cached against the candidate. Defaults to treating every
|
|
54
|
+
* failure as the candidate's, which is the safe assumption.
|
|
55
|
+
*/
|
|
56
|
+
isTransient?: (err: unknown) => boolean;
|
|
57
|
+
buildRecord?: (args: {
|
|
58
|
+
datum: Datum;
|
|
59
|
+
output: Output | null;
|
|
60
|
+
trace: LangChainTrace;
|
|
61
|
+
score: number;
|
|
62
|
+
feedback: string;
|
|
63
|
+
component: string;
|
|
64
|
+
}) => ReflectiveRecord;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Runs a LangChain runnable (chain, agent, or LangGraph graph) as the system
|
|
68
|
+
* under optimization. The candidate is injected by rebuilding the runnable, so
|
|
69
|
+
* anything that reads candidate text — prompts, tool descriptions, routing
|
|
70
|
+
* instructions — is optimizable.
|
|
71
|
+
*/
|
|
72
|
+
declare function createLangChainAdapter<Datum, Output>(options: LangChainAdapterOptions<Datum, Output>): GepaAdapter<Datum, LangChainTrace, Output | null>;
|
|
73
|
+
//#endregion
|
|
74
|
+
export { LangChainAdapterOptions, LangChainEvidence, LangChainScore, LangChainStepType, LangChainTrace, LangChainTraceStep, createLangChainAdapter };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import { mapWithConcurrency, priceUsage } from "textopt";
|
|
2
|
+
//#region src/adapter.ts
|
|
3
|
+
const DEFAULT_CONCURRENCY = 8;
|
|
4
|
+
/**
|
|
5
|
+
* Runs a LangChain runnable (chain, agent, or LangGraph graph) as the system
|
|
6
|
+
* under optimization. The candidate is injected by rebuilding the runnable, so
|
|
7
|
+
* anything that reads candidate text — prompts, tool descriptions, routing
|
|
8
|
+
* instructions — is optimizable.
|
|
9
|
+
*/
|
|
10
|
+
function createLangChainAdapter(options) {
|
|
11
|
+
const { buildRunnable, score, toInput = (datum) => datum, concurrency = DEFAULT_CONCURRENCY, includeChainSteps = false, pricing, componentRunNames, isTransient = () => false, buildRecord } = options;
|
|
12
|
+
return {
|
|
13
|
+
evaluate: async ({ batch, candidate, captureTraces, run, signal }) => {
|
|
14
|
+
const runnable = buildRunnable(candidate);
|
|
15
|
+
const metadata = {
|
|
16
|
+
textopt_iteration: run.iteration,
|
|
17
|
+
textopt_phase: run.phase,
|
|
18
|
+
textopt_split: run.split,
|
|
19
|
+
textopt_candidate_id: run.candidateId
|
|
20
|
+
};
|
|
21
|
+
const results = await mapWithConcurrency({
|
|
22
|
+
items: batch,
|
|
23
|
+
limit: concurrency,
|
|
24
|
+
task: async (datum) => {
|
|
25
|
+
const collector = createTraceCollector({ includeChainSteps });
|
|
26
|
+
const startedAt = Date.now();
|
|
27
|
+
let output = null;
|
|
28
|
+
let failure;
|
|
29
|
+
let transient = false;
|
|
30
|
+
try {
|
|
31
|
+
output = await runnable.invoke(toInput(datum), {
|
|
32
|
+
callbacks: [collector.handler],
|
|
33
|
+
metadata,
|
|
34
|
+
signal
|
|
35
|
+
});
|
|
36
|
+
} catch (err) {
|
|
37
|
+
signal?.throwIfAborted();
|
|
38
|
+
failure = err instanceof Error ? err.message : String(err);
|
|
39
|
+
transient = isTransient(err);
|
|
40
|
+
}
|
|
41
|
+
const steps = collector.finish();
|
|
42
|
+
const usage = priceUsage({
|
|
43
|
+
usage: collector.usage(),
|
|
44
|
+
...pricing === void 0 ? {} : { pricing }
|
|
45
|
+
});
|
|
46
|
+
const trace = {
|
|
47
|
+
steps: captureTraces ? steps : [],
|
|
48
|
+
durationMs: Date.now() - startedAt,
|
|
49
|
+
...failure === void 0 ? {} : { error: failure }
|
|
50
|
+
};
|
|
51
|
+
if (failure !== void 0) return {
|
|
52
|
+
output: null,
|
|
53
|
+
trace,
|
|
54
|
+
usage,
|
|
55
|
+
scored: {
|
|
56
|
+
score: 0,
|
|
57
|
+
feedback: `Run failed: ${failure}`,
|
|
58
|
+
transient
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
try {
|
|
62
|
+
return {
|
|
63
|
+
output,
|
|
64
|
+
trace,
|
|
65
|
+
usage,
|
|
66
|
+
scored: await score({
|
|
67
|
+
datum,
|
|
68
|
+
output,
|
|
69
|
+
trace
|
|
70
|
+
})
|
|
71
|
+
};
|
|
72
|
+
} catch (err) {
|
|
73
|
+
signal?.throwIfAborted();
|
|
74
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
75
|
+
return {
|
|
76
|
+
output,
|
|
77
|
+
trace,
|
|
78
|
+
usage,
|
|
79
|
+
scored: {
|
|
80
|
+
score: 0,
|
|
81
|
+
feedback: `Scoring failed: ${message}`,
|
|
82
|
+
transient: isTransient(err)
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
signal
|
|
88
|
+
});
|
|
89
|
+
const evaluation = {
|
|
90
|
+
outputs: results.map((result) => result.output),
|
|
91
|
+
scores: results.map((result) => result.scored.score),
|
|
92
|
+
feedback: results.map((result) => result.scored.feedback ?? "")
|
|
93
|
+
};
|
|
94
|
+
if (results.some((result) => result.scored.usage !== void 0 || Object.keys(result.usage).length > 0)) evaluation.usage = results.map((result) => result.scored.usage ?? result.usage);
|
|
95
|
+
if (captureTraces) evaluation.trajectories = results.map((result) => result.trace);
|
|
96
|
+
if (results.some((result) => result.scored.objectiveScores !== void 0)) evaluation.objectiveScores = results.map((result) => result.scored.objectiveScores ?? {});
|
|
97
|
+
if (results.some((result) => result.scored.transient === true)) evaluation.transient = results.map((result) => result.scored.transient === true);
|
|
98
|
+
return evaluation;
|
|
99
|
+
},
|
|
100
|
+
makeReflectiveDataset: ({ batch, evaluation, componentsToUpdate }) => {
|
|
101
|
+
const dataset = {};
|
|
102
|
+
for (const component of componentsToUpdate) dataset[component] = batch.map((datum, index) => {
|
|
103
|
+
const trace = evaluation.trajectories?.[index] ?? {
|
|
104
|
+
steps: [],
|
|
105
|
+
durationMs: 0
|
|
106
|
+
};
|
|
107
|
+
const output = evaluation.outputs[index] ?? null;
|
|
108
|
+
const scoreValue = evaluation.scores[index] ?? 0;
|
|
109
|
+
const feedback = evaluation.feedback?.[index] ?? "";
|
|
110
|
+
if (buildRecord !== void 0) return buildRecord({
|
|
111
|
+
datum,
|
|
112
|
+
output,
|
|
113
|
+
trace,
|
|
114
|
+
score: scoreValue,
|
|
115
|
+
feedback,
|
|
116
|
+
component
|
|
117
|
+
});
|
|
118
|
+
const componentSteps = selectComponentSteps({
|
|
119
|
+
trace,
|
|
120
|
+
runName: componentRunNames?.[component]
|
|
121
|
+
});
|
|
122
|
+
return {
|
|
123
|
+
inputs: toInput(datum),
|
|
124
|
+
generatedOutputs: output,
|
|
125
|
+
feedback,
|
|
126
|
+
score: scoreValue,
|
|
127
|
+
...componentSteps.length > 0 ? { evidence: { trace: componentSteps } } : {}
|
|
128
|
+
};
|
|
129
|
+
});
|
|
130
|
+
return dataset;
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
function selectComponentSteps(args) {
|
|
135
|
+
const { trace, runName } = args;
|
|
136
|
+
if (runName === void 0) return trace.steps;
|
|
137
|
+
return trace.steps.filter((step) => step.name === runName);
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* LangChain accepts plain handler objects, so trace capture needs no subclass
|
|
141
|
+
* and no LangSmith account — the same events LangSmith records land here.
|
|
142
|
+
*/
|
|
143
|
+
function createTraceCollector(args) {
|
|
144
|
+
const { includeChainSteps } = args;
|
|
145
|
+
const steps = [];
|
|
146
|
+
const openSteps = /* @__PURE__ */ new Map();
|
|
147
|
+
const tokens = {
|
|
148
|
+
inputTokens: 0,
|
|
149
|
+
outputTokens: 0,
|
|
150
|
+
totalTokens: 0
|
|
151
|
+
};
|
|
152
|
+
let counted = false;
|
|
153
|
+
function open(step) {
|
|
154
|
+
openSteps.set(step.runId, step);
|
|
155
|
+
steps.push(step);
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Any span still open when the run ends never got its end callback — the
|
|
159
|
+
* chain was torn down first. Left alone it is indistinguishable in the trace
|
|
160
|
+
* from a step that legitimately produced nothing, which quietly misleads the
|
|
161
|
+
* reflection model.
|
|
162
|
+
*/
|
|
163
|
+
function finish() {
|
|
164
|
+
for (const step of openSteps.values()) step.error = "Step did not complete before the run ended";
|
|
165
|
+
openSteps.clear();
|
|
166
|
+
return steps;
|
|
167
|
+
}
|
|
168
|
+
function close(args) {
|
|
169
|
+
const step = openSteps.get(args.runId);
|
|
170
|
+
if (step === void 0) return;
|
|
171
|
+
step.outputs = args.outputs;
|
|
172
|
+
if (args.error !== void 0) step.error = args.error;
|
|
173
|
+
openSteps.delete(args.runId);
|
|
174
|
+
}
|
|
175
|
+
const handler = {
|
|
176
|
+
handleLLMStart: (llm, prompts, runId, parentRunId, _extra, _tags, _meta, runName) => {
|
|
177
|
+
open({
|
|
178
|
+
type: "llm",
|
|
179
|
+
name: runName ?? nameOf(llm),
|
|
180
|
+
runId,
|
|
181
|
+
...parentRunId === void 0 ? {} : { parentRunId },
|
|
182
|
+
inputs: prompts
|
|
183
|
+
});
|
|
184
|
+
},
|
|
185
|
+
handleChatModelStart: (llm, messages, runId, parentRunId, _extra, _tags, _meta, runName) => {
|
|
186
|
+
open({
|
|
187
|
+
type: "llm",
|
|
188
|
+
name: runName ?? nameOf(llm),
|
|
189
|
+
runId,
|
|
190
|
+
...parentRunId === void 0 ? {} : { parentRunId },
|
|
191
|
+
inputs: messages.flat().map((message) => ({
|
|
192
|
+
role: message.getType(),
|
|
193
|
+
content: message.content
|
|
194
|
+
}))
|
|
195
|
+
});
|
|
196
|
+
},
|
|
197
|
+
handleLLMEnd: (output, runId) => {
|
|
198
|
+
countTokens(output);
|
|
199
|
+
close({
|
|
200
|
+
runId,
|
|
201
|
+
outputs: output.generations.flat().map((generation) => generation.text)
|
|
202
|
+
});
|
|
203
|
+
},
|
|
204
|
+
handleLLMError: (err, runId) => {
|
|
205
|
+
close({
|
|
206
|
+
runId,
|
|
207
|
+
outputs: null,
|
|
208
|
+
error: messageOf(err)
|
|
209
|
+
});
|
|
210
|
+
},
|
|
211
|
+
handleToolStart: (tool, input, runId, parentRunId, _tags, _meta, runName) => {
|
|
212
|
+
open({
|
|
213
|
+
type: "tool",
|
|
214
|
+
name: runName ?? nameOf(tool),
|
|
215
|
+
runId,
|
|
216
|
+
...parentRunId === void 0 ? {} : { parentRunId },
|
|
217
|
+
inputs: input
|
|
218
|
+
});
|
|
219
|
+
},
|
|
220
|
+
handleToolEnd: (output, runId) => {
|
|
221
|
+
close({
|
|
222
|
+
runId,
|
|
223
|
+
outputs: output
|
|
224
|
+
});
|
|
225
|
+
},
|
|
226
|
+
handleToolError: (err, runId) => {
|
|
227
|
+
close({
|
|
228
|
+
runId,
|
|
229
|
+
outputs: null,
|
|
230
|
+
error: messageOf(err)
|
|
231
|
+
});
|
|
232
|
+
},
|
|
233
|
+
handleRetrieverStart: (retriever, query, runId, parentRunId, _tags, _meta, runName) => {
|
|
234
|
+
open({
|
|
235
|
+
type: "retriever",
|
|
236
|
+
name: runName ?? nameOf(retriever),
|
|
237
|
+
runId,
|
|
238
|
+
...parentRunId === void 0 ? {} : { parentRunId },
|
|
239
|
+
inputs: query
|
|
240
|
+
});
|
|
241
|
+
},
|
|
242
|
+
handleRetrieverEnd: (documents, runId) => {
|
|
243
|
+
close({
|
|
244
|
+
runId,
|
|
245
|
+
outputs: documents.map((document) => document.pageContent)
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
if (includeChainSteps) {
|
|
250
|
+
handler.handleChainStart = (chain, inputs, runId, parentRunId, _tags, _meta, _runType, runName) => {
|
|
251
|
+
open({
|
|
252
|
+
type: "chain",
|
|
253
|
+
name: runName ?? nameOf(chain),
|
|
254
|
+
runId,
|
|
255
|
+
...parentRunId === void 0 ? {} : { parentRunId },
|
|
256
|
+
inputs
|
|
257
|
+
});
|
|
258
|
+
};
|
|
259
|
+
handler.handleChainEnd = (outputs, runId) => {
|
|
260
|
+
close({
|
|
261
|
+
runId,
|
|
262
|
+
outputs
|
|
263
|
+
});
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* LangChain reports tokens in two shapes depending on the integration: the
|
|
268
|
+
* legacy `llmOutput.tokenUsage` and the message-level `usage_metadata` newer
|
|
269
|
+
* chat models attach. Reading both is what makes this work across providers;
|
|
270
|
+
* an integration that fills both describes one call twice, so a message-level
|
|
271
|
+
* shape that carries a count wins and the legacy total is only a fallback.
|
|
272
|
+
*/
|
|
273
|
+
function countTokens(output) {
|
|
274
|
+
let fromMetadata = false;
|
|
275
|
+
for (const generation of output.generations?.flat() ?? []) {
|
|
276
|
+
const metadata = generation.message?.usage_metadata;
|
|
277
|
+
if (metadata === void 0) continue;
|
|
278
|
+
const inputTokens = metadata.input_tokens ?? 0;
|
|
279
|
+
const outputTokens = metadata.output_tokens ?? 0;
|
|
280
|
+
const totalTokens = metadata.total_tokens ?? inputTokens + outputTokens;
|
|
281
|
+
if (inputTokens === 0 && outputTokens === 0 && totalTokens === 0) continue;
|
|
282
|
+
fromMetadata = true;
|
|
283
|
+
counted = true;
|
|
284
|
+
tokens.inputTokens += inputTokens;
|
|
285
|
+
tokens.outputTokens += outputTokens;
|
|
286
|
+
tokens.totalTokens += totalTokens;
|
|
287
|
+
}
|
|
288
|
+
if (fromMetadata) return;
|
|
289
|
+
const legacy = output.llmOutput?.tokenUsage;
|
|
290
|
+
if (legacy !== void 0) {
|
|
291
|
+
counted = true;
|
|
292
|
+
tokens.inputTokens += legacy.promptTokens ?? 0;
|
|
293
|
+
tokens.outputTokens += legacy.completionTokens ?? 0;
|
|
294
|
+
tokens.totalTokens += legacy.totalTokens ?? (legacy.promptTokens ?? 0) + (legacy.completionTokens ?? 0);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
return {
|
|
298
|
+
handler,
|
|
299
|
+
finish,
|
|
300
|
+
usage: () => counted ? { ...tokens } : {}
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
function nameOf(serialized) {
|
|
304
|
+
return serialized?.id?.at(-1) ?? "unknown";
|
|
305
|
+
}
|
|
306
|
+
function messageOf(err) {
|
|
307
|
+
return err instanceof Error ? err.message : String(err);
|
|
308
|
+
}
|
|
309
|
+
//#endregion
|
|
310
|
+
export { createLangChainAdapter };
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@textopt/langchain",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "LangChain adapter for textopt",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"prompt-optimization",
|
|
7
|
+
"gepa",
|
|
8
|
+
"langchain",
|
|
9
|
+
"langgraph",
|
|
10
|
+
"llm"
|
|
11
|
+
],
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"homepage": "https://github.com/ctdio/textopt#readme",
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/ctdio/textopt.git",
|
|
17
|
+
"directory": "packages/langchain"
|
|
18
|
+
},
|
|
19
|
+
"bugs": {
|
|
20
|
+
"url": "https://github.com/ctdio/textopt/issues"
|
|
21
|
+
},
|
|
22
|
+
"type": "module",
|
|
23
|
+
"sideEffects": false,
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=22"
|
|
26
|
+
},
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public"
|
|
29
|
+
},
|
|
30
|
+
"exports": {
|
|
31
|
+
".": {
|
|
32
|
+
"import": {
|
|
33
|
+
"types": "./dist/index.d.mts",
|
|
34
|
+
"default": "./dist/index.mjs"
|
|
35
|
+
},
|
|
36
|
+
"require": {
|
|
37
|
+
"types": "./dist/index.d.cts",
|
|
38
|
+
"default": "./dist/index.cjs"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"main": "./dist/index.cjs",
|
|
43
|
+
"module": "./dist/index.mjs",
|
|
44
|
+
"types": "./dist/index.d.cts",
|
|
45
|
+
"files": [
|
|
46
|
+
"dist"
|
|
47
|
+
],
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"textopt": "^0.1.0"
|
|
50
|
+
},
|
|
51
|
+
"peerDependencies": {
|
|
52
|
+
"@langchain/core": ">=0.3.0 <2"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@langchain/core": "^1.2.8"
|
|
56
|
+
},
|
|
57
|
+
"scripts": {
|
|
58
|
+
"build": "tsdown",
|
|
59
|
+
"typecheck": "tsc --noEmit"
|
|
60
|
+
}
|
|
61
|
+
}
|