@fonoster/autopilot 0.30.0 → 0.31.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/README.md +5 -5
- package/dist/knowledge/MeteredEmbeddings.d.ts +47 -0
- package/dist/knowledge/MeteredEmbeddings.js +58 -0
- package/dist/models/createLanguageModel.d.ts +2 -2
- package/dist/models/createLanguageModel.js +2 -2
- package/dist/models/evaluations/createEvalEffectiveConfig.d.ts +6 -6
- package/dist/models/evaluations/createEvalEffectiveConfig.js +7 -7
- package/dist/models/evaluations/createEvaluateIntelligence.d.ts +15 -4
- package/dist/models/evaluations/createEvaluateIntelligence.js +33 -12
- package/dist/models/evaluations/createTestTextSimilarity.d.ts +7 -1
- package/dist/models/evaluations/createTestTextSimilarity.js +19 -1
- package/dist/models/evaluations/evalTestCases.d.ts +2 -2
- package/dist/models/evaluations/evalTestCases.js +7 -7
- package/dist/models/evaluations/evaluateScenario.js +2 -2
- package/dist/models/evaluations/evaluateStep.d.ts +1 -1
- package/dist/models/evaluations/evaluateStep.js +2 -2
- package/dist/models/evaluations/extractAiResponse.d.ts +2 -2
- package/dist/models/evaluations/extractAiResponse.js +3 -3
- package/dist/models/evaluations/runEval.d.ts +2 -2
- package/dist/models/evaluations/runEval.js +7 -7
- package/dist/models/evaluations/session/EvalSession.d.ts +1 -1
- package/dist/models/evaluations/session/EvalSession.js +1 -1
- package/dist/models/evaluations/session/evaluateSessionScenario.d.ts +3 -3
- package/dist/models/evaluations/session/evaluateSessionScenario.js +4 -4
- package/dist/models/evaluations/session/runSessionEval.d.ts +7 -4
- package/dist/models/evaluations/session/runSessionEval.js +7 -7
- package/dist/models/evaluations/textSimilaryPrompt.d.ts +1 -1
- package/dist/models/evaluations/textSimilaryPrompt.js +1 -1
- package/dist/models/evaluations/types.d.ts +5 -5
- package/dist/models/types.d.ts +3 -3
- package/dist/session/Agent.d.ts +38 -3
- package/dist/session/Agent.js +90 -16
- package/dist/session/config.d.ts +2 -2
- package/dist/session/config.js +4 -4
- package/dist/session/createAutopilot.d.ts +12 -11
- package/dist/session/createAutopilot.js +48 -16
- package/dist/session/hosted.d.ts +23 -25
- package/dist/session/hosted.js +43 -48
- package/dist/session/index.d.ts +1 -1
- package/dist/session/index.js +1 -1
- package/dist/session/knowledge.d.ts +1 -1
- package/dist/session/llm.d.ts +28 -1
- package/dist/session/llm.js +50 -6
- package/dist/session/main.js +2 -1
- package/dist/session/meteredThink.d.ts +45 -0
- package/dist/session/meteredThink.js +125 -0
- package/dist/session/telemetry.d.ts +34 -0
- package/dist/session/telemetry.js +55 -0
- package/dist/session/tools.d.ts +1 -1
- package/dist/session/tools.js +1 -1
- package/package.json +9 -9
- package/dist/session/convertLegacyAssistant.d.ts +0 -77
- package/dist/session/convertLegacyAssistant.js +0 -118
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ The autopilot module provides a way to create Voice AI applications in Fonoster.
|
|
|
15
15
|
|
|
16
16
|
## Installation
|
|
17
17
|
|
|
18
|
-
Autopilot is an ordinary session app that
|
|
18
|
+
Autopilot is an ordinary session app that AppBridge dials. Run the
|
|
19
19
|
`fonoster/autopilot` image (built from `mods/autopilot/Dockerfile`), or from the repository:
|
|
20
20
|
|
|
21
21
|
```sh-session
|
|
@@ -23,7 +23,7 @@ $ cp config/autopilot-session.example.yaml config/autopilot-session.yaml
|
|
|
23
23
|
$ node mods/autopilot/dist/session/main.js --config config/autopilot-session.yaml
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
-
See [docs/autopilot.md](../../docs/autopilot.md) for the engine, pacing and the
|
|
26
|
+
See [docs/autopilot.md](../../docs/autopilot.md) for the engine, pacing and the agent
|
|
27
27
|
schema.
|
|
28
28
|
|
|
29
29
|
## Example
|
|
@@ -64,7 +64,7 @@ The configuration file has two major sections: `conversationSettings` and `langu
|
|
|
64
64
|
To run the Autopilot, you can use the following command:
|
|
65
65
|
|
|
66
66
|
```sh-session
|
|
67
|
-
$
|
|
67
|
+
$ AGENT=$(pwd)/agents.json OPEN_API_KEY=your-key autopilot
|
|
68
68
|
```
|
|
69
69
|
|
|
70
70
|
You will see a message like this:
|
|
@@ -187,8 +187,8 @@ For example:
|
|
|
187
187
|
|
|
188
188
|
Autopilot reads `autopilot-session.yaml`, found via `--config <path>`, the
|
|
189
189
|
`AUTOPILOT_SESSION_CONFIG` variable or `./config/autopilot-session.yaml`. It names the signing
|
|
190
|
-
secret
|
|
190
|
+
secret AppBridge uses for Autopilot apps, where apps and agents come from, the registry
|
|
191
191
|
catalog, platform keys by credential ref and the knowledge settings. See
|
|
192
192
|
`config/autopilot-session.example.yaml` for every key.
|
|
193
193
|
|
|
194
|
-
Audio filters are part of the app's configuration (
|
|
194
|
+
Audio filters are part of the app's configuration (AppBridge runs them), not the agent's.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (C) 2025 by Fonoster Inc (https://fonoster.com)
|
|
3
|
+
* http://github.com/fonoster/fonoster
|
|
4
|
+
*
|
|
5
|
+
* This file is part of Fonoster
|
|
6
|
+
*
|
|
7
|
+
* Licensed under the MIT License (the "License");
|
|
8
|
+
* you may not use this file except in compliance with
|
|
9
|
+
* the License. You may obtain a copy of the License at
|
|
10
|
+
*
|
|
11
|
+
* https://opensource.org/licenses/MIT
|
|
12
|
+
*
|
|
13
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
+
* See the License for the specific language governing permissions and
|
|
17
|
+
* limitations under the License.
|
|
18
|
+
*/
|
|
19
|
+
import { Usage } from "@fonoster/common";
|
|
20
|
+
/** Where embedding usage goes (the session's usage sink). */
|
|
21
|
+
export type EmbeddingUsageSink = {
|
|
22
|
+
usage(lines: Usage.UsageLine[]): void;
|
|
23
|
+
missing(catalogId: string): void;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* OpenAI embeddings that report their token usage (add-usage-metering): the knowledge base's
|
|
27
|
+
* embeddings, called directly so the vendor's `usage.prompt_tokens` is not lost. Same defaults
|
|
28
|
+
* as LangChain's OpenAIEmbeddings (model text-embedding-ada-002, 512 texts per request); it
|
|
29
|
+
* implements the embedDocuments/embedQuery pair LangChain's vector stores use.
|
|
30
|
+
*/
|
|
31
|
+
export declare class MeteredEmbeddings {
|
|
32
|
+
private readonly options;
|
|
33
|
+
private readonly model;
|
|
34
|
+
private readonly baseUrl;
|
|
35
|
+
constructor(options: {
|
|
36
|
+
apiKey: string;
|
|
37
|
+
keySource: Usage.KeySource;
|
|
38
|
+
sink?: EmbeddingUsageSink;
|
|
39
|
+
model?: string;
|
|
40
|
+
baseUrl?: string;
|
|
41
|
+
batchSize?: number;
|
|
42
|
+
});
|
|
43
|
+
get catalogId(): string;
|
|
44
|
+
embedDocuments(texts: string[]): Promise<number[][]>;
|
|
45
|
+
embedQuery(text: string): Promise<number[]>;
|
|
46
|
+
private embed;
|
|
47
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MeteredEmbeddings = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* OpenAI embeddings that report their token usage (add-usage-metering): the knowledge base's
|
|
6
|
+
* embeddings, called directly so the vendor's `usage.prompt_tokens` is not lost. Same defaults
|
|
7
|
+
* as LangChain's OpenAIEmbeddings (model text-embedding-ada-002, 512 texts per request); it
|
|
8
|
+
* implements the embedDocuments/embedQuery pair LangChain's vector stores use.
|
|
9
|
+
*/
|
|
10
|
+
class MeteredEmbeddings {
|
|
11
|
+
constructor(options) {
|
|
12
|
+
this.options = options;
|
|
13
|
+
this.model = options.model ?? "text-embedding-ada-002";
|
|
14
|
+
this.baseUrl = (options.baseUrl ?? "https://api.openai.com").replace(/\/$/, "");
|
|
15
|
+
}
|
|
16
|
+
get catalogId() {
|
|
17
|
+
return `openai/${this.model}`;
|
|
18
|
+
}
|
|
19
|
+
async embedDocuments(texts) {
|
|
20
|
+
const size = this.options.batchSize ?? 512;
|
|
21
|
+
const batches = Array.from({ length: Math.ceil(texts.length / size) }, (_, i) => texts.slice(i * size, (i + 1) * size));
|
|
22
|
+
const vectors = await Promise.all(batches.map((b) => this.embed(b)));
|
|
23
|
+
return vectors.flat();
|
|
24
|
+
}
|
|
25
|
+
async embedQuery(text) {
|
|
26
|
+
return (await this.embed([text]))[0];
|
|
27
|
+
}
|
|
28
|
+
async embed(input) {
|
|
29
|
+
const res = await fetch(`${this.baseUrl}/v1/embeddings`, {
|
|
30
|
+
method: "POST",
|
|
31
|
+
headers: {
|
|
32
|
+
"content-type": "application/json",
|
|
33
|
+
authorization: `Bearer ${this.options.apiKey}`
|
|
34
|
+
},
|
|
35
|
+
body: JSON.stringify({ model: this.model, input })
|
|
36
|
+
});
|
|
37
|
+
if (!res.ok)
|
|
38
|
+
throw new Error(`embeddings answered HTTP ${res.status}`);
|
|
39
|
+
const body = (await res.json());
|
|
40
|
+
const tokens = body.usage?.prompt_tokens;
|
|
41
|
+
if (tokens === undefined)
|
|
42
|
+
this.options.sink?.missing(this.catalogId);
|
|
43
|
+
else
|
|
44
|
+
this.options.sink?.usage([
|
|
45
|
+
{
|
|
46
|
+
meter: "embedding.tokens",
|
|
47
|
+
quantity: tokens,
|
|
48
|
+
provider: "openai",
|
|
49
|
+
model: this.catalogId,
|
|
50
|
+
keySource: this.options.keySource
|
|
51
|
+
}
|
|
52
|
+
]);
|
|
53
|
+
return [...body.data]
|
|
54
|
+
.sort((a, b) => a.index - b.index)
|
|
55
|
+
.map((d) => d.embedding);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
exports.MeteredEmbeddings = MeteredEmbeddings;
|
|
@@ -16,10 +16,10 @@
|
|
|
16
16
|
* See the License for the specific language governing permissions and
|
|
17
17
|
* limitations under the License.
|
|
18
18
|
*/
|
|
19
|
-
import {
|
|
19
|
+
import { EvalAgent, KnowledgeBase, TelephonyContext, Voice } from "..";
|
|
20
20
|
declare function createLanguageModel(params: {
|
|
21
21
|
voice: Voice;
|
|
22
|
-
|
|
22
|
+
agentConfig: EvalAgent;
|
|
23
23
|
knowledgeBase: KnowledgeBase;
|
|
24
24
|
telephonyContext: TelephonyContext;
|
|
25
25
|
}): import("./AbstractLanguageModel").AbstractLanguageModel;
|
|
@@ -21,8 +21,8 @@ exports.createLanguageModel = createLanguageModel;
|
|
|
21
21
|
*/
|
|
22
22
|
const __1 = require("..");
|
|
23
23
|
function createLanguageModel(params) {
|
|
24
|
-
const { voice,
|
|
25
|
-
const { languageModel, conversationSettings } =
|
|
24
|
+
const { voice, agentConfig, knowledgeBase, telephonyContext } = params;
|
|
25
|
+
const { languageModel, conversationSettings } = agentConfig;
|
|
26
26
|
// The transfer tool is only added if the transfer options exist
|
|
27
27
|
const tools = languageModel.tools.concat(conversationSettings.transferOptions
|
|
28
28
|
? [__1.hangupToolDefinition, __1.transferToolDefinition]
|
|
@@ -16,13 +16,13 @@
|
|
|
16
16
|
* See the License for the specific language governing permissions and
|
|
17
17
|
* limitations under the License.
|
|
18
18
|
*/
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
19
|
+
import { SessionAgent } from "@fonoster/common";
|
|
20
|
+
import { EvalAgent } from "../types";
|
|
21
21
|
/**
|
|
22
|
-
* The session
|
|
23
|
-
* with the keys the caller resolved (never stored in the
|
|
22
|
+
* The session agent as the evaluations run it: the catalog id split into provider and model,
|
|
23
|
+
* with the keys the caller resolved (never stored in the agent).
|
|
24
24
|
*/
|
|
25
|
-
export declare function createEvalEffectiveConfig(
|
|
25
|
+
export declare function createEvalEffectiveConfig(agent: SessionAgent, keys: {
|
|
26
26
|
apiKey?: string;
|
|
27
27
|
evaluationApiKey?: string;
|
|
28
|
-
}):
|
|
28
|
+
}): EvalAgent;
|
|
@@ -28,20 +28,20 @@ const LLM_PROVIDERS = [
|
|
|
28
28
|
"anthropic"
|
|
29
29
|
];
|
|
30
30
|
/**
|
|
31
|
-
* The session
|
|
32
|
-
* with the keys the caller resolved (never stored in the
|
|
31
|
+
* The session agent as the evaluations run it: the catalog id split into provider and model,
|
|
32
|
+
* with the keys the caller resolved (never stored in the agent).
|
|
33
33
|
*/
|
|
34
|
-
function createEvalEffectiveConfig(
|
|
35
|
-
const [provider, ...rest] =
|
|
34
|
+
function createEvalEffectiveConfig(agent, keys) {
|
|
35
|
+
const [provider, ...rest] = agent.languageModel.model.split("/");
|
|
36
36
|
if (!LLM_PROVIDERS.includes(provider))
|
|
37
37
|
throw new Error(`evaluations do not support ${provider} models`);
|
|
38
|
-
const testCases = common_1.testCasesSchema.parse(
|
|
39
|
-
const { provider: _p, apiKey: _k, baseUrl: _b, ...languageModel } =
|
|
38
|
+
const testCases = common_1.testCasesSchema.parse(agent.testCases);
|
|
39
|
+
const { provider: _p, apiKey: _k, baseUrl: _b, ...languageModel } = agent.languageModel;
|
|
40
40
|
void _p;
|
|
41
41
|
void _k;
|
|
42
42
|
void _b;
|
|
43
43
|
return {
|
|
44
|
-
conversationSettings:
|
|
44
|
+
conversationSettings: agent.conversationSettings,
|
|
45
45
|
languageModel: {
|
|
46
46
|
...languageModel,
|
|
47
47
|
provider: provider,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Usage } from "@fonoster/common";
|
|
1
2
|
import { EvaluateIntelligenceRequest } from "./types";
|
|
2
3
|
type ServerStreamCall = {
|
|
3
4
|
request: EvaluateIntelligenceRequest;
|
|
@@ -8,10 +9,20 @@ type ServerStreamCall = {
|
|
|
8
9
|
* A model provider's key for a workspace: the workspace's own vendor key, else the platform key
|
|
9
10
|
* by the provider's credentialRef (the control plane decides; integrations.json is gone).
|
|
10
11
|
*/
|
|
11
|
-
export type EvalKeySource = (providerId: string, workspaceRef: string) => Promise<string | undefined>;
|
|
12
|
+
export type EvalKeySource = (providerId: string, workspaceRef: string) => Promise<EvalKey | string | undefined>;
|
|
13
|
+
/** A key and whose it is; a bare string is the platform's. */
|
|
14
|
+
export type EvalKey = {
|
|
15
|
+
key: string;
|
|
16
|
+
source: Usage.KeySource;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Where an eval's usage goes (add-usage-metering D5): the workspace, the lines, and a scope that
|
|
20
|
+
* is unique per request within the run, for the lines' ids.
|
|
21
|
+
*/
|
|
22
|
+
export type EvalUsageReporter = (workspaceRef: string, lines: Usage.UsageLine[], scope: string) => void;
|
|
12
23
|
/**
|
|
13
|
-
* Runs an
|
|
14
|
-
* catalog id picks the provider, and keys come from `keys`, never from the
|
|
24
|
+
* Runs an agent's test cases (rework-autopilot 8.2: the agent is a session agent; its
|
|
25
|
+
* catalog id picks the provider, and keys come from `keys`, never from the agent).
|
|
15
26
|
*/
|
|
16
|
-
declare function createEvaluateIntelligence(keys: EvalKeySource): (call: ServerStreamCall) => Promise<void>;
|
|
27
|
+
declare function createEvaluateIntelligence(keys: EvalKeySource, reportUsage?: EvalUsageReporter): (call: ServerStreamCall) => Promise<void>;
|
|
17
28
|
export { createEvaluateIntelligence };
|
|
@@ -19,33 +19,45 @@ exports.createEvaluateIntelligence = createEvaluateIntelligence;
|
|
|
19
19
|
* See the License for the specific language governing permissions and
|
|
20
20
|
* limitations under the License.
|
|
21
21
|
*/
|
|
22
|
+
const crypto_1 = require("crypto");
|
|
22
23
|
const common_1 = require("@fonoster/common");
|
|
23
24
|
const logger_1 = require("@fonoster/logger");
|
|
24
25
|
const pb_util_1 = require("pb-util");
|
|
26
|
+
const meteredThink_1 = require("../../session/meteredThink");
|
|
25
27
|
const createEvalEffectiveConfig_1 = require("./createEvalEffectiveConfig");
|
|
26
28
|
const runEval_1 = require("./runEval");
|
|
27
29
|
const session_1 = require("./session");
|
|
28
30
|
const stepReportToEventPayload_1 = require("./stepReportToEventPayload");
|
|
29
31
|
const logger = (0, logger_1.getLogger)({ service: "apiserver", filePath: __filename });
|
|
32
|
+
const keyOf = (k) => typeof k === "string" ? { key: k, source: "platform" } : k;
|
|
30
33
|
/**
|
|
31
|
-
* Runs an
|
|
32
|
-
* catalog id picks the provider, and keys come from `keys`, never from the
|
|
34
|
+
* Runs an agent's test cases (rework-autopilot 8.2: the agent is a session agent; its
|
|
35
|
+
* catalog id picks the provider, and keys come from `keys`, never from the agent).
|
|
33
36
|
*/
|
|
34
|
-
function createEvaluateIntelligence(keys) {
|
|
37
|
+
function createEvaluateIntelligence(keys, reportUsage) {
|
|
35
38
|
const evaluateIntelligence = async (call) => {
|
|
36
39
|
const { request } = call;
|
|
37
40
|
const { intelligence } = request;
|
|
38
41
|
const accessKeyId = (0, common_1.getAccessKeyIdFromCall)(call);
|
|
39
42
|
try {
|
|
40
43
|
const config = pb_util_1.struct.decode(intelligence.config);
|
|
41
|
-
const
|
|
42
|
-
const provider =
|
|
44
|
+
const agent = common_1.sessionAgentSchema.parse(config);
|
|
45
|
+
const provider = agent.languageModel.model.split("/")[0];
|
|
43
46
|
logger.verbose("call to evaluateIntelligence", {
|
|
44
47
|
accessKeyId,
|
|
45
|
-
model:
|
|
48
|
+
model: agent.languageModel.model
|
|
46
49
|
});
|
|
47
|
-
const
|
|
48
|
-
const
|
|
50
|
+
const agentKey = keyOf(await keys(provider, accessKeyId));
|
|
51
|
+
const judgeKey = keyOf(await keys("openai", accessKeyId));
|
|
52
|
+
const apiKey = agentKey?.key;
|
|
53
|
+
const evaluationApiKey = judgeKey?.key;
|
|
54
|
+
// Eval tokens are metered with origin eval, one scope per request of this run.
|
|
55
|
+
const runId = (0, crypto_1.randomUUID)();
|
|
56
|
+
let request = 0;
|
|
57
|
+
const sink = {
|
|
58
|
+
usage: (lines) => reportUsage?.(accessKeyId, lines, `eval|${runId}|${request++}`),
|
|
59
|
+
missing: (catalogId) => logger.warn(`eval: ${catalogId} reported no usage`, { accessKeyId })
|
|
60
|
+
};
|
|
49
61
|
if (!apiKey && provider !== "ollama")
|
|
50
62
|
throw new Error(`no ${provider} key for this workspace or platform`);
|
|
51
63
|
if (!evaluationApiKey)
|
|
@@ -60,12 +72,21 @@ function createEvaluateIntelligence(keys) {
|
|
|
60
72
|
};
|
|
61
73
|
// The session engine (streaming replies, structured tool messages) for OpenAI-compatible
|
|
62
74
|
// providers; the LangChain path stays for the rest until their adapters land (EV-B1).
|
|
63
|
-
const
|
|
64
|
-
if (
|
|
65
|
-
|
|
75
|
+
const sessionThink = (0, session_1.sessionThinkFor)(agent, apiKey);
|
|
76
|
+
if (sessionThink) {
|
|
77
|
+
const think = (0, meteredThink_1.meteredThink)(sessionThink, {
|
|
78
|
+
catalogId: agent.languageModel.model,
|
|
79
|
+
keySource: agentKey?.source ?? "platform"
|
|
80
|
+
}, sink);
|
|
81
|
+
await (0, session_1.runSessionEval)({
|
|
82
|
+
agent,
|
|
83
|
+
think,
|
|
84
|
+
evaluationApiKey,
|
|
85
|
+
judgeUsage: { sink, keySource: judgeKey?.source ?? "platform" }
|
|
86
|
+
}, callbacks);
|
|
66
87
|
return;
|
|
67
88
|
}
|
|
68
|
-
const effectiveConfig = (0, createEvalEffectiveConfig_1.createEvalEffectiveConfig)(
|
|
89
|
+
const effectiveConfig = (0, createEvalEffectiveConfig_1.createEvalEffectiveConfig)(agent, {
|
|
69
90
|
apiKey,
|
|
70
91
|
evaluationApiKey
|
|
71
92
|
});
|
|
@@ -1,6 +1,12 @@
|
|
|
1
|
+
import { MeteredModel, UsageSink } from "../../session/meteredThink";
|
|
1
2
|
export declare function createTestTextSimilarity(evalsLanguageModel: {
|
|
2
3
|
provider: any;
|
|
3
4
|
model: string;
|
|
4
5
|
baseUrl?: string;
|
|
5
6
|
apiKey?: string;
|
|
6
|
-
}, systemPrompt: string
|
|
7
|
+
}, systemPrompt: string,
|
|
8
|
+
/** Meters the judge's tokens (add-usage-metering); the judge is always OpenAI. */
|
|
9
|
+
usage?: {
|
|
10
|
+
sink: UsageSink;
|
|
11
|
+
keySource: MeteredModel["keySource"];
|
|
12
|
+
}): (text1: string, text2: string) => Promise<boolean>;
|
|
@@ -21,7 +21,10 @@ exports.createTestTextSimilarity = createTestTextSimilarity;
|
|
|
21
21
|
*/
|
|
22
22
|
const messages_1 = require("@langchain/core/messages");
|
|
23
23
|
const openai_1 = require("@langchain/openai");
|
|
24
|
-
|
|
24
|
+
const meteredThink_1 = require("../../session/meteredThink");
|
|
25
|
+
function createTestTextSimilarity(evalsLanguageModel, systemPrompt,
|
|
26
|
+
/** Meters the judge's tokens (add-usage-metering); the judge is always OpenAI. */
|
|
27
|
+
usage) {
|
|
25
28
|
if (!evalsLanguageModel.apiKey) {
|
|
26
29
|
throw new Error("API key is required for text similarity evaluation.");
|
|
27
30
|
}
|
|
@@ -37,6 +40,21 @@ function createTestTextSimilarity(evalsLanguageModel, systemPrompt) {
|
|
|
37
40
|
new messages_1.HumanMessage(`Text 1: ${text1}\nText 2: ${text2}`)
|
|
38
41
|
];
|
|
39
42
|
const response = await llm.invoke(messages);
|
|
43
|
+
if (usage) {
|
|
44
|
+
const model = {
|
|
45
|
+
catalogId: `openai/${evalsLanguageModel.model}`,
|
|
46
|
+
keySource: usage.keySource
|
|
47
|
+
};
|
|
48
|
+
const m = response.usage_metadata;
|
|
49
|
+
if (m)
|
|
50
|
+
usage.sink.usage((0, meteredThink_1.usageLines)(model, {
|
|
51
|
+
inputTokens: m.input_tokens,
|
|
52
|
+
outputTokens: m.output_tokens,
|
|
53
|
+
cachedTokens: m.input_token_details?.cache_read ?? 0
|
|
54
|
+
}));
|
|
55
|
+
else
|
|
56
|
+
usage.sink.missing(model.catalogId);
|
|
57
|
+
}
|
|
40
58
|
const reply = response.content?.toString().trim().toLowerCase();
|
|
41
59
|
return reply === "true" || reply === "yes";
|
|
42
60
|
};
|
|
@@ -17,9 +17,9 @@
|
|
|
17
17
|
* limitations under the License.
|
|
18
18
|
*/
|
|
19
19
|
import { ScenarioEvaluationReport } from "@fonoster/types";
|
|
20
|
-
import {
|
|
20
|
+
import { EvalAgent } from "../types";
|
|
21
21
|
export declare function evalTestCases(autopilotApplication: {
|
|
22
22
|
intelligence: {
|
|
23
|
-
config:
|
|
23
|
+
config: EvalAgent;
|
|
24
24
|
};
|
|
25
25
|
}): Promise<ScenarioEvaluationReport[]>;
|
|
@@ -7,7 +7,7 @@ const evaluateScenario_1 = require("./evaluateScenario");
|
|
|
7
7
|
const textSimilaryPrompt_1 = require("./textSimilaryPrompt");
|
|
8
8
|
async function evalTestCases(autopilotApplication) {
|
|
9
9
|
const { testCases } = autopilotApplication.intelligence.config;
|
|
10
|
-
const { config:
|
|
10
|
+
const { config: agentConfig } = autopilotApplication.intelligence;
|
|
11
11
|
const voice = {
|
|
12
12
|
say: async (_) => { }
|
|
13
13
|
};
|
|
@@ -17,7 +17,7 @@ async function evalTestCases(autopilotApplication) {
|
|
|
17
17
|
const scenario = scenarios[i];
|
|
18
18
|
const languageModel = (0, createLanguageModel_1.createLanguageModel)({
|
|
19
19
|
voice,
|
|
20
|
-
|
|
20
|
+
agentConfig: autopilotApplication.intelligence.config,
|
|
21
21
|
knowledgeBase: {
|
|
22
22
|
load: async () => { },
|
|
23
23
|
queryKnowledgeBase: async (query) => query
|
|
@@ -25,12 +25,12 @@ async function evalTestCases(autopilotApplication) {
|
|
|
25
25
|
telephonyContext: scenario.telephonyContext
|
|
26
26
|
});
|
|
27
27
|
const testTextSimilarity = (0, createTestTextSimilarity_1.createTestTextSimilarity)({
|
|
28
|
-
provider:
|
|
29
|
-
model:
|
|
30
|
-
apiKey:
|
|
31
|
-
},
|
|
28
|
+
provider: agentConfig.testCases?.evalsLanguageModel?.provider,
|
|
29
|
+
model: agentConfig.testCases?.evalsLanguageModel?.model ?? "",
|
|
30
|
+
apiKey: agentConfig.testCases?.evalsLanguageModel?.apiKey
|
|
31
|
+
}, agentConfig.testCases?.evalsSystemPrompt || textSimilaryPrompt_1.textSimilaryPrompt);
|
|
32
32
|
const evaluationReport = await (0, evaluateScenario_1.evaluateScenario)({
|
|
33
|
-
|
|
33
|
+
agentConfig,
|
|
34
34
|
scenario,
|
|
35
35
|
languageModel,
|
|
36
36
|
testTextSimilarity
|
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.evaluateScenario = evaluateScenario;
|
|
4
4
|
const evaluateStep_1 = require("./evaluateStep");
|
|
5
5
|
async function evaluateScenario(config, onStepResult) {
|
|
6
|
-
const { scenario, languageModel, testTextSimilarity,
|
|
6
|
+
const { scenario, languageModel, testTextSimilarity, agentConfig } = config;
|
|
7
7
|
const results = [];
|
|
8
8
|
const steps = scenario.conversation;
|
|
9
9
|
for (let i = 0; i < steps.length; i++) {
|
|
@@ -12,7 +12,7 @@ async function evaluateScenario(config, onStepResult) {
|
|
|
12
12
|
step,
|
|
13
13
|
languageModel,
|
|
14
14
|
testTextSimilarity,
|
|
15
|
-
|
|
15
|
+
agentConfig
|
|
16
16
|
});
|
|
17
17
|
results.push(stepResult);
|
|
18
18
|
if (onStepResult)
|
|
@@ -18,4 +18,4 @@
|
|
|
18
18
|
*/
|
|
19
19
|
import { StepEvaluationReport } from "@fonoster/types";
|
|
20
20
|
import { EvaluateStepParams } from "./types";
|
|
21
|
-
export declare function evaluateStep({ step, languageModel, testTextSimilarity,
|
|
21
|
+
export declare function evaluateStep({ step, languageModel, testTextSimilarity, agentConfig }: EvaluateStepParams): Promise<StepEvaluationReport>;
|
|
@@ -4,7 +4,7 @@ exports.evaluateStep = evaluateStep;
|
|
|
4
4
|
const evaluateTextResponse_1 = require("./evaluateTextResponse");
|
|
5
5
|
const evaluateToolCalls_1 = require("./evaluateToolCalls");
|
|
6
6
|
const extractAiResponse_1 = require("./extractAiResponse");
|
|
7
|
-
async function evaluateStep({ step, languageModel, testTextSimilarity,
|
|
7
|
+
async function evaluateStep({ step, languageModel, testTextSimilarity, agentConfig }) {
|
|
8
8
|
const stepResult = {
|
|
9
9
|
humanInput: step.userInput,
|
|
10
10
|
expectedResponse: step.expected.text.response,
|
|
@@ -14,7 +14,7 @@ async function evaluateStep({ step, languageModel, testTextSimilarity, assistant
|
|
|
14
14
|
};
|
|
15
15
|
try {
|
|
16
16
|
const response = await languageModel.invoke(step.userInput);
|
|
17
|
-
stepResult.aiResponse = (0, extractAiResponse_1.extractAiResponse)(response,
|
|
17
|
+
stepResult.aiResponse = (0, extractAiResponse_1.extractAiResponse)(response, agentConfig);
|
|
18
18
|
const textEvaluation = await (0, evaluateTextResponse_1.evaluateTextResponse)({
|
|
19
19
|
type: step.expected.text.type,
|
|
20
20
|
response: step.expected.text.response
|
|
@@ -16,9 +16,9 @@
|
|
|
16
16
|
* See the License for the specific language governing permissions and
|
|
17
17
|
* limitations under the License.
|
|
18
18
|
*/
|
|
19
|
-
import {
|
|
19
|
+
import { EvalAgent, InvocationResult } from "../types";
|
|
20
20
|
/**
|
|
21
21
|
* Maps language model invocation result to the text that should be compared
|
|
22
22
|
* in eval (hangup/transfer use config messages).
|
|
23
23
|
*/
|
|
24
|
-
export declare function extractAiResponse(response: InvocationResult,
|
|
24
|
+
export declare function extractAiResponse(response: InvocationResult, agentConfig: EvalAgent): string;
|
|
@@ -5,14 +5,14 @@ exports.extractAiResponse = extractAiResponse;
|
|
|
5
5
|
* Maps language model invocation result to the text that should be compared
|
|
6
6
|
* in eval (hangup/transfer use config messages).
|
|
7
7
|
*/
|
|
8
|
-
function extractAiResponse(response,
|
|
8
|
+
function extractAiResponse(response, agentConfig) {
|
|
9
9
|
if (response.toolCalls && response.toolCalls.length > 0) {
|
|
10
10
|
const topTool = response.toolCalls[0];
|
|
11
11
|
if (topTool.name === "hangup") {
|
|
12
|
-
return
|
|
12
|
+
return agentConfig.conversationSettings?.goodbyeMessage ?? "";
|
|
13
13
|
}
|
|
14
14
|
if (topTool.name === "transfer") {
|
|
15
|
-
return
|
|
15
|
+
return agentConfig.conversationSettings?.transferOptions?.message ?? "";
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
return response.content ?? "";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { EvalAgent } from "../types";
|
|
2
2
|
import { RunEvalCallbacks } from "./types";
|
|
3
3
|
export type RunEvalConfig = {
|
|
4
4
|
intelligence: {
|
|
5
|
-
config:
|
|
5
|
+
config: EvalAgent;
|
|
6
6
|
};
|
|
7
7
|
};
|
|
8
8
|
export declare function runEval(autopilotApplication: RunEvalConfig, callbacks: RunEvalCallbacks): Promise<void>;
|
|
@@ -7,7 +7,7 @@ const evaluateScenario_1 = require("./evaluateScenario");
|
|
|
7
7
|
const textSimilaryPrompt_1 = require("./textSimilaryPrompt");
|
|
8
8
|
async function runEval(autopilotApplication, callbacks) {
|
|
9
9
|
const { testCases } = autopilotApplication.intelligence.config;
|
|
10
|
-
const
|
|
10
|
+
const agentConfig = autopilotApplication.intelligence.config;
|
|
11
11
|
const voice = {
|
|
12
12
|
say: async (_) => { }
|
|
13
13
|
};
|
|
@@ -15,7 +15,7 @@ async function runEval(autopilotApplication, callbacks) {
|
|
|
15
15
|
for (const scenario of scenarios) {
|
|
16
16
|
const languageModel = (0, createLanguageModel_1.createLanguageModel)({
|
|
17
17
|
voice,
|
|
18
|
-
|
|
18
|
+
agentConfig,
|
|
19
19
|
knowledgeBase: {
|
|
20
20
|
load: async () => { },
|
|
21
21
|
queryKnowledgeBase: async (query) => query
|
|
@@ -23,12 +23,12 @@ async function runEval(autopilotApplication, callbacks) {
|
|
|
23
23
|
telephonyContext: scenario.telephonyContext
|
|
24
24
|
});
|
|
25
25
|
const testTextSimilarity = (0, createTestTextSimilarity_1.createTestTextSimilarity)({
|
|
26
|
-
provider:
|
|
27
|
-
model:
|
|
28
|
-
apiKey:
|
|
29
|
-
},
|
|
26
|
+
provider: agentConfig.testCases?.evalsLanguageModel?.provider,
|
|
27
|
+
model: agentConfig.testCases?.evalsLanguageModel?.model ?? "",
|
|
28
|
+
apiKey: agentConfig.testCases?.evalsLanguageModel?.apiKey
|
|
29
|
+
}, agentConfig.testCases?.evalsSystemPrompt ?? textSimilaryPrompt_1.textSimilaryPrompt);
|
|
30
30
|
const report = await (0, evaluateScenario_1.evaluateScenario)({
|
|
31
|
-
|
|
31
|
+
agentConfig,
|
|
32
32
|
scenario,
|
|
33
33
|
languageModel,
|
|
34
34
|
testTextSimilarity
|
|
@@ -26,7 +26,7 @@ export type EvalTelephony = {
|
|
|
26
26
|
};
|
|
27
27
|
/**
|
|
28
28
|
* An in-memory session for conversation evals (rework-autopilot 1.4): the session engine runs on
|
|
29
|
-
* it exactly as on a call, but there is no
|
|
29
|
+
* it exactly as on a call, but there is no AppBridge, socket or audio. Turns go in as `turn.ended`;
|
|
30
30
|
* every reply is "heard" in full (no barge-in in an eval).
|
|
31
31
|
*/
|
|
32
32
|
export declare class EvalSession {
|
|
@@ -23,7 +23,7 @@ const events_1 = require("events");
|
|
|
23
23
|
const voice_1 = require("@fonoster/voice");
|
|
24
24
|
/**
|
|
25
25
|
* An in-memory session for conversation evals (rework-autopilot 1.4): the session engine runs on
|
|
26
|
-
* it exactly as on a call, but there is no
|
|
26
|
+
* it exactly as on a call, but there is no AppBridge, socket or audio. Turns go in as `turn.ended`;
|
|
27
27
|
* every reply is "heard" in full (no barge-in in an eval).
|
|
28
28
|
*/
|
|
29
29
|
class EvalSession {
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* limitations under the License.
|
|
18
18
|
*/
|
|
19
19
|
import { ScenarioEvaluationReport, StepEvaluationReport } from "@fonoster/types";
|
|
20
|
-
import {
|
|
20
|
+
import { SessionAgent } from "../../../session/config";
|
|
21
21
|
import { ChatMessage, Think } from "../../../session/think";
|
|
22
22
|
import { ToolPort } from "../../../session/tools";
|
|
23
23
|
import { EvalScenario } from "../types";
|
|
@@ -36,12 +36,12 @@ export type SessionScenarioReport = ScenarioEvaluationReport & {
|
|
|
36
36
|
steps: SessionStepReport[];
|
|
37
37
|
};
|
|
38
38
|
export type SessionEvalOptions = {
|
|
39
|
-
|
|
39
|
+
agent: SessionAgent;
|
|
40
40
|
/** The language model the engine streams from (a registry adapter, or the fake LLM). */
|
|
41
41
|
think: Think;
|
|
42
42
|
testTextSimilarity: (text1: string, text2: string) => Promise<boolean>;
|
|
43
43
|
/**
|
|
44
|
-
* Runs the
|
|
44
|
+
* Runs the agent's own tools. Defaults to `evalToolPort`: evals never call tool endpoints
|
|
45
45
|
* (an eval must not book an appointment), every call succeeds with `{ status: "ok" }`.
|
|
46
46
|
*/
|
|
47
47
|
tools?: ToolPort;
|
|
@@ -41,7 +41,7 @@ function parseArgs(call) {
|
|
|
41
41
|
* read back from the structured tool messages in the history.
|
|
42
42
|
*/
|
|
43
43
|
async function evaluateSessionScenario(options, scenario, onStepResult) {
|
|
44
|
-
const {
|
|
44
|
+
const { agent: agentConfig, testTextSimilarity } = options;
|
|
45
45
|
const stepTimeoutMs = options.stepTimeoutMs ?? 60000;
|
|
46
46
|
const session = new EvalSession_1.EvalSession(scenario.telephonyContext);
|
|
47
47
|
let agent;
|
|
@@ -64,7 +64,7 @@ async function evaluateSessionScenario(options, scenario, onStepResult) {
|
|
|
64
64
|
})()
|
|
65
65
|
};
|
|
66
66
|
const handler = (0, createAutopilot_1.createAutopilotHandler)({
|
|
67
|
-
|
|
67
|
+
agent: { ...agentConfig, testCases: undefined },
|
|
68
68
|
think,
|
|
69
69
|
tools: options.tools ?? exports.evalToolPort,
|
|
70
70
|
recorder: {
|
|
@@ -134,8 +134,8 @@ async function evaluateSessionScenario(options, scenario, onStepResult) {
|
|
|
134
134
|
// engine says for it (as the legacy evals compared).
|
|
135
135
|
report.aiResponse = builtIn
|
|
136
136
|
? builtIn.name === "hangup"
|
|
137
|
-
?
|
|
138
|
-
: (
|
|
137
|
+
? agentConfig.conversationSettings.goodbyeMessage
|
|
138
|
+
: (agentConfig.conversationSettings.transferOptions?.message ?? "")
|
|
139
139
|
: reply
|
|
140
140
|
.map((s) => s.text)
|
|
141
141
|
.join("")
|