@peopl-health/nexus 4.7.0-dev.0 → 4.7.0-dev.2
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.
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
const { OpenAI } = require('openai');
|
|
2
2
|
|
|
3
|
+
const { retryWithBackoff } = require('../../utils/retryUtils');
|
|
4
|
+
const { withTiming } = require('../../utils/tracingDecorator');
|
|
5
|
+
const { safeParse } = require('../../utils/jsonUtils');
|
|
6
|
+
|
|
3
7
|
const { OpenAIResponsesProvider } = require('./OpenAIResponsesProvider');
|
|
8
|
+
const { handleFunctionCalls } = require('./OpenAIResponsesProviderTools');
|
|
4
9
|
|
|
5
10
|
const OPENROUTER_BASE_URL = 'https://openrouter.ai/api/v1';
|
|
6
11
|
const DEFAULT_MODEL_VENDOR = 'openai';
|
|
@@ -30,9 +35,146 @@ class OpenRouterProvider extends OpenAIResponsesProvider {
|
|
|
30
35
|
this.transcriptionClient = null;
|
|
31
36
|
}
|
|
32
37
|
|
|
38
|
+
async _invokeModel({
|
|
39
|
+
instructions, input, toolSchemas = [], toolChoice = 'auto', followUpToolChoice = 'auto',
|
|
40
|
+
modelConfig, assistant, phiProcessor = null, trace = null
|
|
41
|
+
}, options = {}) {
|
|
42
|
+
const executeTools = options.toolExecutor || handleFunctionCalls;
|
|
43
|
+
let totalRetries = 0;
|
|
44
|
+
const allToolsExecuted = [];
|
|
45
|
+
const allToolCalls = [];
|
|
46
|
+
const accumulatedUsage = { input_tokens: 0, output_tokens: 0, total_tokens: 0 };
|
|
47
|
+
const addUsage = (usage) => {
|
|
48
|
+
if (!usage) return;
|
|
49
|
+
accumulatedUsage.input_tokens += usage.prompt_tokens || 0;
|
|
50
|
+
accumulatedUsage.output_tokens += usage.completion_tokens || 0;
|
|
51
|
+
accumulatedUsage.total_tokens += usage.total_tokens || 0;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const messages = this._toChatMessages(instructions, input);
|
|
55
|
+
const apiCallConfig = { ...this._mapModelConfig(modelConfig) };
|
|
56
|
+
if (toolSchemas.length > 0) {
|
|
57
|
+
apiCallConfig.tools = toolSchemas;
|
|
58
|
+
apiCallConfig.tool_choice = toolChoice;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const makeAPICall = (msgs) => retryWithBackoff(
|
|
62
|
+
() => this.client.chat.completions.create({ messages: msgs, ...apiCallConfig }),
|
|
63
|
+
{ providerName: this.constructor.name }
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
const { result: { result: response, retries }, durationMs } = await withTiming(() => makeAPICall(messages));
|
|
67
|
+
totalRetries += retries;
|
|
68
|
+
addUsage(response.usage);
|
|
69
|
+
let producingIteration = trace?.addIteration({ response, durationMs, apiCallConfig }) || null;
|
|
70
|
+
let finalResponse = response;
|
|
71
|
+
const currentMessages = [...messages];
|
|
72
|
+
|
|
73
|
+
if (assistant && toolSchemas.length > 0) {
|
|
74
|
+
apiCallConfig.tool_choice = followUpToolChoice;
|
|
75
|
+
|
|
76
|
+
for (let round = 1; round <= this.maxFunctionRounds; round++) {
|
|
77
|
+
const message = finalResponse.choices?.[0]?.message;
|
|
78
|
+
const toolCalls = message?.tool_calls || [];
|
|
79
|
+
if (!toolCalls.length) break;
|
|
80
|
+
|
|
81
|
+
const functionCalls = toolCalls.map(tc => ({
|
|
82
|
+
name: tc.function.name,
|
|
83
|
+
arguments: tc.function.arguments,
|
|
84
|
+
call_id: tc.id,
|
|
85
|
+
}));
|
|
86
|
+
allToolCalls.push(...toolCalls.map(tc => ({ name: tc.function.name, arguments: safeParse(tc.function.arguments), call_id: tc.id })));
|
|
87
|
+
const { outputs, toolsExecuted } = await executeTools(functionCalls, assistant, phiProcessor, producingIteration);
|
|
88
|
+
|
|
89
|
+
currentMessages.push(
|
|
90
|
+
message,
|
|
91
|
+
...outputs.map(o => ({ role: 'tool', tool_call_id: o.call_id, content: this._toToolContent(o.output) }))
|
|
92
|
+
);
|
|
93
|
+
allToolsExecuted.push(...toolsExecuted);
|
|
94
|
+
|
|
95
|
+
const { result: { result: followUp, retries: followUpRetries }, durationMs: followUpDurationMs } = await withTiming(() => makeAPICall(currentMessages));
|
|
96
|
+
totalRetries += followUpRetries;
|
|
97
|
+
addUsage(followUp.usage);
|
|
98
|
+
producingIteration = trace?.addIteration({ response: followUp, durationMs: followUpDurationMs, apiCallConfig }) || null;
|
|
99
|
+
finalResponse = followUp;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return { response: this._normalizeResponse(finalResponse), toolCalls: allToolCalls, toolsExecuted: allToolsExecuted, retries: totalRetries, usage: accumulatedUsage };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
_toChatMessages(instructions, input) {
|
|
107
|
+
const systemParts = [];
|
|
108
|
+
if (instructions) systemParts.push(instructions);
|
|
109
|
+
|
|
110
|
+
const outputsByCallId = new Map();
|
|
111
|
+
for (const item of input || []) {
|
|
112
|
+
if (item.type === 'function_call_output') outputsByCallId.set(item.call_id, item.output);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const messages = [];
|
|
116
|
+
for (const item of input || []) {
|
|
117
|
+
const type = item.type || 'message';
|
|
118
|
+
if (type === 'function_call') {
|
|
119
|
+
if (!outputsByCallId.has(item.call_id)) continue;
|
|
120
|
+
messages.push({
|
|
121
|
+
role: 'assistant',
|
|
122
|
+
content: null,
|
|
123
|
+
tool_calls: [{ id: item.call_id, type: 'function', function: { name: item.name, arguments: item.arguments || '{}' } }],
|
|
124
|
+
});
|
|
125
|
+
messages.push({ role: 'tool', tool_call_id: item.call_id, content: this._toToolContent(outputsByCallId.get(item.call_id)) });
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
if (type === 'function_call_output') continue;
|
|
129
|
+
const content = item.content;
|
|
130
|
+
if (item.role === 'developer' || item.role === 'system') {
|
|
131
|
+
if (content) systemParts.push(content);
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
messages.push({ role: item.role === 'assistant' ? 'assistant' : 'user', content });
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const system = systemParts.join('\n\n');
|
|
138
|
+
return system ? [{ role: 'system', content: system }, ...messages] : messages;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
_toToolContent(output) {
|
|
142
|
+
return typeof output === 'string' ? output : JSON.stringify(output ?? '');
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
_normalizeResponse(response) {
|
|
146
|
+
const choice = response.choices?.[0];
|
|
147
|
+
const outputText = (choice?.message?.content || '').trim();
|
|
148
|
+
const output = outputText
|
|
149
|
+
? [{ type: 'message', role: 'assistant', content: [{ type: 'output_text', text: outputText }] }]
|
|
150
|
+
: [];
|
|
151
|
+
const usage = response.usage
|
|
152
|
+
? {
|
|
153
|
+
input_tokens: response.usage.prompt_tokens || 0,
|
|
154
|
+
output_tokens: response.usage.completion_tokens || 0,
|
|
155
|
+
total_tokens: response.usage.total_tokens || 0,
|
|
156
|
+
}
|
|
157
|
+
: undefined;
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
id: response.id,
|
|
161
|
+
object: 'response',
|
|
162
|
+
model: response.model,
|
|
163
|
+
status: choice?.finish_reason === 'content_filter' ? 'refused' : 'completed',
|
|
164
|
+
stop_reason: choice?.finish_reason,
|
|
165
|
+
output,
|
|
166
|
+
output_text: outputText,
|
|
167
|
+
usage,
|
|
168
|
+
raw: response,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
33
172
|
_mapModelConfig(modelConfig) {
|
|
34
|
-
const mapped = { ...modelConfig };
|
|
173
|
+
const mapped = { ...(modelConfig || {}) };
|
|
174
|
+
if (mapped.max_tokens == null && mapped.max_output_tokens != null) mapped.max_tokens = mapped.max_output_tokens;
|
|
35
175
|
delete mapped.store;
|
|
176
|
+
delete mapped.text;
|
|
177
|
+
delete mapped.max_output_tokens;
|
|
36
178
|
mapped.model = this._toOpenRouterModel(mapped.model || this.defaults.responseModel);
|
|
37
179
|
return mapped;
|
|
38
180
|
}
|