feat: add Russian localization and independent STT languages
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import { learningLanguageInstruction, type Language } from "../../core/i18n.ts";
|
||||
import { Type } from "typebox";
|
||||
import type { AgentTool } from "@earendil-works/pi-agent-core";
|
||||
import type { Usage } from "@earendil-works/pi-ai";
|
||||
@@ -31,6 +32,7 @@ export interface DemoBuilderInput {
|
||||
}
|
||||
|
||||
export interface DemoBuilderOptions {
|
||||
language?: Language;
|
||||
/** Test seam: scripted engine; production uses the demoBuilder role. */
|
||||
engine?: PiEngine;
|
||||
headless?: HeadlessApi;
|
||||
@@ -207,7 +209,7 @@ export async function runDemoBuilder(input: DemoBuilderInput, opts: DemoBuilderO
|
||||
const result = await runSubagent(Object.values(createDemoBuilderTools(state, headless, opts.onActivity)), {
|
||||
role: "demoBuilder",
|
||||
engine: opts.engine,
|
||||
systemPrompt: INSTRUCTIONS,
|
||||
systemPrompt: `${INSTRUCTIONS}\n\n${learningLanguageInstruction(opts.language ?? "en")}`,
|
||||
prompt,
|
||||
maxTurns: opts.maxSteps ?? 24,
|
||||
abortSignal: opts.abortSignal,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { learningLanguageInstruction, type Language } from "../../core/i18n.ts";
|
||||
import * as crypto from "node:crypto";
|
||||
import type { Usage } from "@earendil-works/pi-ai";
|
||||
import type { AgentTool } from "@earendil-works/pi-agent-core";
|
||||
@@ -471,6 +472,7 @@ function runExtractor(
|
||||
batchIds: ReadonlySet<string>,
|
||||
signal: AbortSignal | undefined,
|
||||
onStep: (usage: Usage, model: string, step: number, finishReason: string, tools: string) => void,
|
||||
language: Language = "en",
|
||||
): Promise<SessionFindings> {
|
||||
// Chains in (the wire), chains out (the store) — no translation seam
|
||||
// since chains-primary. Validation runs at the submit seam (error-back)
|
||||
@@ -486,7 +488,7 @@ function runExtractor(
|
||||
parameters: sessionFindingsSchema,
|
||||
fill: fillFindings,
|
||||
validate: (value) => validateSessionAnchors(value, batchIds) ?? validateFindingsChains(value, existingIds),
|
||||
systemPrompt: EXTRACTOR_INSTRUCTIONS,
|
||||
systemPrompt: `${EXTRACTOR_INSTRUCTIONS}\n\n${learningLanguageInstruction(language)}`,
|
||||
prompt,
|
||||
engine,
|
||||
signal,
|
||||
@@ -507,6 +509,7 @@ function runNotesExtractor(
|
||||
existingIds: ReadonlySet<string>,
|
||||
signal: AbortSignal | undefined,
|
||||
onStep: (usage: Usage, model: string, step: number, finishReason: string, tools: string) => void,
|
||||
language: Language = "en",
|
||||
): Promise<NotesFindings> {
|
||||
return runSubmitAgent<FindingsSubmit>({
|
||||
role: "extractor",
|
||||
@@ -517,7 +520,7 @@ function runNotesExtractor(
|
||||
parameters: findingsSchema,
|
||||
fill: fillNotesFindings,
|
||||
validate: (value) => validateFindingsChains(value, existingIds),
|
||||
systemPrompt: NOTES_EXTRACTOR_INSTRUCTIONS,
|
||||
systemPrompt: `${NOTES_EXTRACTOR_INSTRUCTIONS}\n\n${learningLanguageInstruction(language)}`,
|
||||
prompt,
|
||||
engine,
|
||||
signal,
|
||||
@@ -725,6 +728,7 @@ export interface ExtractionModels {
|
||||
}
|
||||
|
||||
export interface ExtractionPipelineDeps extends GraphDeps, ExtractionModels {
|
||||
languageForSource?: (sourceId: string) => Language;
|
||||
/** The chunk budget — the transcript-token share of the extractor's
|
||||
* context; batches fill to it (the trigger fires on the same number,
|
||||
* read fresh per turn by the composition root). OPTIONAL with the
|
||||
@@ -735,6 +739,7 @@ export interface ExtractionPipelineDeps extends GraphDeps, ExtractionModels {
|
||||
}
|
||||
|
||||
export interface NotesImportInput {
|
||||
language?: Language;
|
||||
/** The source's file path — its identity (deterministic src id) AND its
|
||||
* provenance label in the detail card. */
|
||||
filePath: string;
|
||||
@@ -940,6 +945,7 @@ export function createExtractionPipeline(deps: ExtractionPipelineDeps): Extracti
|
||||
recordStep(jobKey, "extractor", callUsage(usage), model);
|
||||
console.info(`[extraction] extractor step ${step}: ${finishReason}, tools: ${tools}`);
|
||||
},
|
||||
deps.languageForSource?.(sourceId) ?? chatStore.getChat(sourceId)?.language ?? "en",
|
||||
);
|
||||
console.info(`[extraction ${jobKey}] extractor done in ${Math.round(performance.now() - t0)}ms`);
|
||||
return findings;
|
||||
@@ -1262,7 +1268,7 @@ export function createExtractionPipeline(deps: ExtractionPipelineDeps): Extracti
|
||||
|
||||
async function notesExtract(
|
||||
jobKey: string,
|
||||
input: { id: string; filePath: string; text: string; part?: { i: number; n: number } },
|
||||
input: { id: string; filePath: string; text: string; language?: Language; part?: { i: number; n: number } },
|
||||
): Promise<NotesFindings> {
|
||||
// jobKey (per-chunk jobs, owner 2026-09-28): the registry key —
|
||||
// "src#i" for chunk jobs — so the abort signal reaches the call.
|
||||
@@ -1290,6 +1296,7 @@ export function createExtractionPipeline(deps: ExtractionPipelineDeps): Extracti
|
||||
recordStep(jobKey, "extractor", callUsage(usage), model);
|
||||
console.info(`[extraction] extractor step ${step}: ${finishReason}, tools: ${tools}`);
|
||||
},
|
||||
input.language ?? deps.languageForSource?.(input.id) ?? "en",
|
||||
);
|
||||
console.info(`[extraction ${jobKey}] notes extractor done in ${Math.round(performance.now() - t0)}ms`);
|
||||
return findings;
|
||||
@@ -1298,7 +1305,7 @@ export function createExtractionPipeline(deps: ExtractionPipelineDeps): Extracti
|
||||
// The notes auditor is the SAME identity-linker as the chat one (one
|
||||
// schema now that attribution is gone) — audit() above, verbatim.
|
||||
|
||||
async function runNotesExtraction(input: { id: string; filePath: string; text: string; importedAt: string }): Promise<EmissionStats> {
|
||||
async function runNotesExtraction(input: { id: string; filePath: string; text: string; importedAt: string; language?: Language }): Promise<EmissionStats> {
|
||||
const { id } = input;
|
||||
// importNotes owns the source exclusion, including metadata and cleanup.
|
||||
const t0 = performance.now();
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { learningLanguageInstruction, type Language } from "../../core/i18n.ts";
|
||||
import { randomUUID } from "node:crypto";
|
||||
import type { AssistantMessage, Message, ToolResultMessage, Usage, Tool } from "@earendil-works/pi-ai";
|
||||
import { normalizeContext } from "@earendil-works/pi-ai";
|
||||
@@ -62,6 +63,7 @@ const RENDER_BATCH_CAP = 6;
|
||||
const EXTRACT_CAP = 24_000;
|
||||
|
||||
export interface PdfReaderOptions {
|
||||
language?: Language;
|
||||
engine?: PiEngine;
|
||||
abortSignal?: AbortSignal;
|
||||
onUsage?: (usage: Usage) => void;
|
||||
@@ -206,7 +208,7 @@ export async function runPdfReaderAgent(
|
||||
for (let iteration = 0; iteration < maxIterations; iteration++) {
|
||||
const result = await streamOnce(
|
||||
engine,
|
||||
INSTRUCTIONS,
|
||||
`${INSTRUCTIONS}\n\n${learningLanguageInstruction(opts.language ?? "en")}`,
|
||||
messages,
|
||||
toolDeclarations,
|
||||
sessionId,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { learningLanguageInstruction, type Language } from "../../core/i18n.ts";
|
||||
import type { AgentTool } from "@earendil-works/pi-agent-core";
|
||||
import type { Usage } from "@earendil-works/pi-ai";
|
||||
import { createWebTools } from "../tools/web.ts";
|
||||
@@ -68,6 +69,7 @@ Numbered findings with inline source citations:
|
||||
What couldn't be answered. Suggested next steps.`;
|
||||
|
||||
export interface ResearcherRunOptions {
|
||||
language?: Language;
|
||||
engine?: PiEngine;
|
||||
/** The search seam (search port wave, 2026-09-20): resolves the LIVE
|
||||
* provider per call, null = honestly unavailable (the web_search tool
|
||||
@@ -98,7 +100,7 @@ export async function runResearcherAgent(prompt: string, opts: ResearcherRunOpti
|
||||
role: "researcher",
|
||||
prompt,
|
||||
engine: opts.engine,
|
||||
systemPrompt: INSTRUCTIONS,
|
||||
systemPrompt: `${INSTRUCTIONS}\n\n${learningLanguageInstruction(opts.language ?? "en")}`,
|
||||
maxTurns: 12,
|
||||
abortSignal: opts.abortSignal,
|
||||
onUsage: opts.onUsage,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { Language } from "../../core/i18n.ts";
|
||||
import type { PiEngine } from "../models.ts";
|
||||
import type { SearchProvider } from "../../core/search.ts";
|
||||
import {
|
||||
@@ -64,6 +65,7 @@ export const pdfModelOverride: { current?: PiEngine } = {};
|
||||
type JobToolInput = ResearchToolInput | DemoToolInput | VisualToolInput | PdfToolInput;
|
||||
|
||||
export interface JobRunnerDeps {
|
||||
languageForChat?: (chatId: string) => Language;
|
||||
chatStore: ChatStore;
|
||||
jobStore: JobStore;
|
||||
/**
|
||||
@@ -364,6 +366,7 @@ export function createJobRunner(deps: JobRunnerDeps): JobRunner {
|
||||
jobStore.updateJob(jobId, { activity: "researching" });
|
||||
const brief = await runResearcherAgent(prompt, {
|
||||
engine: model,
|
||||
language: deps.languageForChat?.(job.chatId) ?? chatStore.getChat(job.chatId)?.language ?? "en",
|
||||
search: deps.searchProvider,
|
||||
pdfDir,
|
||||
abortSignal: loop.abortSignal,
|
||||
@@ -417,6 +420,7 @@ export function createJobRunner(deps: JobRunnerDeps): JobRunner {
|
||||
{ title, brief },
|
||||
{
|
||||
engine: model,
|
||||
language: deps.languageForChat?.(job.chatId) ?? chatStore.getChat(job.chatId)?.language ?? "en",
|
||||
abortSignal: loop.abortSignal,
|
||||
chatId: loop.chatId,
|
||||
onUsage: (usage) => loop.onUsage(piUsageToStep(usage)),
|
||||
@@ -453,6 +457,7 @@ export function createJobRunner(deps: JobRunnerDeps): JobRunner {
|
||||
{ brief },
|
||||
{
|
||||
engine: model,
|
||||
language: deps.languageForChat?.(job.chatId) ?? chatStore.getChat(job.chatId)?.language ?? "en",
|
||||
abortSignal: loop.abortSignal,
|
||||
chatId: loop.chatId,
|
||||
onUsage: (usage) => loop.onUsage(piUsageToStep(usage)),
|
||||
@@ -492,6 +497,7 @@ export function createJobRunner(deps: JobRunnerDeps): JobRunner {
|
||||
{ path, question },
|
||||
{
|
||||
engine: model,
|
||||
language: deps.languageForChat?.(job.chatId) ?? chatStore.getChat(job.chatId)?.language ?? "en",
|
||||
abortSignal: loop.abortSignal,
|
||||
chatId: loop.chatId,
|
||||
onUsage: (usage) => loop.onUsage(piUsageToStep(usage)),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { learningLanguageInstruction, type Language } from "../../core/i18n.ts";
|
||||
import { randomUUID } from "node:crypto";
|
||||
import type { AssistantMessage, Message, Tool, ToolResultMessage, Usage } from "@earendil-works/pi-ai";
|
||||
import { normalizeContext } from "@earendil-works/pi-ai";
|
||||
@@ -35,6 +36,7 @@ export interface VisualMakerInput {
|
||||
}
|
||||
|
||||
export interface VisualMakerOptions {
|
||||
language?: Language;
|
||||
/** Test seam: scripted engine; production uses the visualMaker role. */
|
||||
engine?: PiEngine;
|
||||
headless?: HeadlessApi;
|
||||
@@ -163,7 +165,7 @@ export async function runVisualMaker(input: VisualMakerInput, opts: VisualMakerO
|
||||
// loop owns tool execution and the image-in-the-loop injection).
|
||||
const result = await streamOnce(
|
||||
engine,
|
||||
INSTRUCTIONS,
|
||||
`${INSTRUCTIONS}\n\n${learningLanguageInstruction(opts.language ?? "en")}`,
|
||||
messages,
|
||||
toolDeclarations,
|
||||
sessionId,
|
||||
|
||||
Reference in New Issue
Block a user