mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-10-25 17:36:32 +08:00
Merge pull request #1243 from Steve0x2a/fix/omit-temperature-gpt-6-responses
fix: omit temperature for gpt-6 under openai-responses
This commit is contained in:
5 files changed
+156
-10
No files matched your search
@@ -10,6 +10,7 @@ import {
|
||||
} from './agentMemoryService'
|
||||
import { createAgentTextRedactionSession } from './agentTextRedaction'
|
||||
import { filterAutomaticMemoryEntries, filterAutomaticMemoryPatch, type AgentMemoryQualityIssue } from './agentMemoryQuality'
|
||||
import { omitsTemperature } from './agentSamplingOptions'
|
||||
import type { AgentFeedbackMemoryEvidence, AgentFeedbackMemorySample, AgentFeedbackReason } from './agentFeedbackStore'
|
||||
import type { AgentModelConfig } from './agentService'
|
||||
|
||||
@@ -66,9 +67,7 @@ function latestUserText(messages: unknown[]): string {
|
||||
}
|
||||
|
||||
function memorySamplingOptions(config: AgentModelConfig): { temperature?: number } {
|
||||
const modelId = String(config.model || '').trim().toLocaleLowerCase()
|
||||
const reasoningModel = config.protocol === 'openai-responses' && /^(?:gpt-5|o[1-4](?:-|$))/.test(modelId)
|
||||
return reasoningModel || config.protocol === 'anthropic' ? {} : { temperature: 0.1 }
|
||||
return omitsTemperature(config) ? {} : { temperature: 0.1 }
|
||||
}
|
||||
|
||||
function modelIsConfigured(config?: AgentModelConfig | null): config is AgentModelConfig {
|
||||
|
||||
@@ -59,6 +59,7 @@ import {
|
||||
} from './agentMemoryRuntimeGuidance'
|
||||
import { createAgentModel } from './agentModelAdapter'
|
||||
import { truncateStringToSchemaMax } from './agentModelSafety'
|
||||
import { omitsTemperature } from './agentSamplingOptions'
|
||||
import {
|
||||
AgentRunStore,
|
||||
assessAgentRunResume,
|
||||
@@ -851,9 +852,7 @@ function providerOptions(config: AgentModelConfig, promptCacheKey: string): Prov
|
||||
}
|
||||
|
||||
function samplingOptions(config: AgentModelConfig, temperature: number): { temperature?: number } {
|
||||
const model = text(config.model).toLocaleLowerCase()
|
||||
if (config.protocol === 'anthropic' || (config.protocol === 'openai-responses' && /^(?:gpt-5|o[1-4](?:-|$))/.test(model))) return {}
|
||||
return { temperature }
|
||||
return omitsTemperature(config) ? {} : { temperature }
|
||||
}
|
||||
|
||||
function compactToolOutputForStorage(toolName: string, output: unknown): unknown {
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
export type AgentSamplingConfig = {
|
||||
protocol?: string
|
||||
model?: string
|
||||
}
|
||||
|
||||
const OPENAI_RESPONSES_REASONING_MODEL = /^(?:gpt-(?:5|6)|o[1-4](?:-|$))/
|
||||
|
||||
export function omitsTemperature(config: AgentSamplingConfig): boolean {
|
||||
if (config.protocol === 'anthropic') return true
|
||||
const modelId = String(config.model || '').trim().toLocaleLowerCase()
|
||||
return config.protocol === 'openai-responses' && OPENAI_RESPONSES_REASONING_MODEL.test(modelId)
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { streamText } from 'ai'
|
||||
import { createAgentModel } from './agentModelAdapter'
|
||||
import { omitsTemperature } from './agentSamplingOptions'
|
||||
import { createAgentTextRedactionSession } from './agentTextRedaction'
|
||||
import type { AgentModelConfig } from './agentService'
|
||||
|
||||
@@ -19,11 +20,8 @@ function fallbackTitle(value: string): string {
|
||||
|
||||
function titleSamplingOptions(config: AgentModelConfig): { temperature?: number } {
|
||||
const modelId = String(config.model || '').trim().toLocaleLowerCase()
|
||||
const openAiReasoningModel = config.protocol === 'openai-responses'
|
||||
&& /^(?:gpt-5|o[1-4](?:-|$))/.test(modelId)
|
||||
const nativeAnthropicModel = config.protocol === 'anthropic'
|
||||
const modernClaudeModel = /(?:^|\/)claude-(?:opus-4-(?:[7-9]|\d{2,})|(?:sonnet|opus|haiku|fable|mythos)-(?:[5-9]|\d{2,})(?:-|$))/.test(modelId)
|
||||
return openAiReasoningModel || nativeAnthropicModel || modernClaudeModel ? {} : { temperature: 0.2 }
|
||||
return omitsTemperature(config) || modernClaudeModel ? {} : { temperature: 0.2 }
|
||||
}
|
||||
|
||||
function titleErrorMessage(error: unknown): string {
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
// Exercise the production omit-temperature predicate without loading Electron or native modules.
|
||||
const assert = require('node:assert/strict')
|
||||
const fs = require('node:fs')
|
||||
const path = require('node:path')
|
||||
const vm = require('node:vm')
|
||||
const { test } = require('node:test')
|
||||
|
||||
const root = path.join(__dirname, '..')
|
||||
const helperPath = path.join(root, 'electron/services/agentSamplingOptions.ts')
|
||||
const ledPath = path.join(root, 'electron/services/agentModelLedService.ts')
|
||||
const titlePath = path.join(root, 'electron/services/agentTitleService.ts')
|
||||
const memoryPath = path.join(root, 'electron/services/agentMemorySynthesisService.ts')
|
||||
|
||||
function extractFunction(source, name) {
|
||||
const header = new RegExp(`function ${name}\\([^)]*\\)(?:\\s*:\\s*\\{[^}]*\\})?\\s*\\{`)
|
||||
const match = header.exec(source)
|
||||
assert.ok(match, `missing function ${name}`)
|
||||
const start = match.index
|
||||
const brace = start + match[0].length - 1
|
||||
let depth = 0
|
||||
for (let index = brace; index < source.length; index += 1) {
|
||||
const char = source[index]
|
||||
if (char === '{') depth += 1
|
||||
else if (char === '}') {
|
||||
depth -= 1
|
||||
if (depth === 0) return source.slice(start, index + 1)
|
||||
}
|
||||
}
|
||||
throw new Error(`unterminated function ${name}`)
|
||||
}
|
||||
|
||||
function stripTypes(source) {
|
||||
return source
|
||||
.replace(/\bexport type [A-Za-z0-9_]+ = \{[\s\S]*?\n\}\n/, '')
|
||||
.replace(/\bexport /g, '')
|
||||
.replace(/: Agent(?:Sampling|Model)Config/g, '')
|
||||
.replace(/: \{ protocol\?: string; model\?: string \}/g, '')
|
||||
.replace(/temperature: number/g, 'temperature')
|
||||
.replace(/\): boolean/g, ')')
|
||||
.replace(/\): \{ temperature\?: number \}/g, ')')
|
||||
}
|
||||
|
||||
function loadHelper() {
|
||||
const source = fs.readFileSync(helperPath, 'utf8')
|
||||
const js = stripTypes(source)
|
||||
const module = { exports: {} }
|
||||
vm.runInNewContext(`${js}\nmodule.exports = { omitsTemperature }`, {
|
||||
module,
|
||||
exports: module.exports,
|
||||
})
|
||||
return module.exports
|
||||
}
|
||||
|
||||
function loadOmitsTemperature() {
|
||||
if (fs.existsSync(helperPath)) return loadHelper().omitsTemperature
|
||||
|
||||
const led = extractFunction(fs.readFileSync(ledPath, 'utf8'), 'samplingOptions')
|
||||
const title = extractFunction(fs.readFileSync(titlePath, 'utf8'), 'titleSamplingOptions')
|
||||
const memory = extractFunction(fs.readFileSync(memoryPath, 'utf8'), 'memorySamplingOptions')
|
||||
const js = `
|
||||
function text(value) { return String(value ?? '').replace(/\\u0000/g, '').trim() }
|
||||
${stripTypes(led)}
|
||||
${stripTypes(title)}
|
||||
${stripTypes(memory)}
|
||||
function omitsTemperature(config) {
|
||||
return !('temperature' in samplingOptions(config, 0.7))
|
||||
&& !('temperature' in titleSamplingOptions(config))
|
||||
&& !('temperature' in memorySamplingOptions(config))
|
||||
}
|
||||
`
|
||||
const module = { exports: {} }
|
||||
vm.runInNewContext(`${js}\nmodule.exports = { omitsTemperature }`, { module, exports: module.exports })
|
||||
return module.exports.omitsTemperature
|
||||
}
|
||||
|
||||
function loadSamplingOptions() {
|
||||
const omits = loadOmitsTemperature()
|
||||
return (config, temperature) => (omits(config) ? {} : { temperature })
|
||||
}
|
||||
|
||||
const omitsTemperature = loadOmitsTemperature()
|
||||
const samplingOptions = loadSamplingOptions()
|
||||
const plain = (value) => JSON.parse(JSON.stringify(value))
|
||||
|
||||
function responses(model) {
|
||||
return { protocol: 'openai-responses', model }
|
||||
}
|
||||
|
||||
test('openai-responses gpt-6-luna omits temperature', () => {
|
||||
assert.equal(omitsTemperature(responses('gpt-6-luna')), true)
|
||||
assert.deepEqual(plain(samplingOptions(responses('gpt-6-luna'), 0.7)), {})
|
||||
})
|
||||
|
||||
test('openai-responses gpt-6-* variants omit temperature', () => {
|
||||
for (const model of ['gpt-6', 'gpt-6-something', 'GPT-6-Luna']) {
|
||||
assert.equal(omitsTemperature(responses(model)), true, model)
|
||||
assert.deepEqual(plain(samplingOptions(responses(model), 0.2)), {})
|
||||
}
|
||||
})
|
||||
|
||||
test('openai-responses gpt-5 and o-series still omit temperature', () => {
|
||||
for (const model of ['gpt-5', 'gpt-5.5', 'gpt-5.4-mini', 'o1', 'o1-preview', 'o3', 'o4-mini']) {
|
||||
assert.equal(omitsTemperature(responses(model)), true, model)
|
||||
assert.deepEqual(plain(samplingOptions(responses(model), 0.7)), {})
|
||||
}
|
||||
})
|
||||
|
||||
test('non-reasoning openai-responses still includes temperature', () => {
|
||||
for (const model of ['gpt-4o', 'gpt-4.1', 'chatgpt-4o-latest']) {
|
||||
assert.equal(omitsTemperature(responses(model)), false, model)
|
||||
assert.deepEqual(plain(samplingOptions(responses(model), 0.7)), { temperature: 0.7 })
|
||||
}
|
||||
})
|
||||
|
||||
test('openai-compatible gpt-6-luna still includes temperature', () => {
|
||||
const config = { protocol: 'openai-compatible', model: 'gpt-6-luna' }
|
||||
assert.equal(omitsTemperature(config), false)
|
||||
assert.deepEqual(plain(samplingOptions(config, 0.3)), { temperature: 0.3 })
|
||||
})
|
||||
|
||||
test('anthropic path still omits temperature for any model', () => {
|
||||
for (const model of ['claude-sonnet-4', 'gpt-4o', 'gpt-6-luna']) {
|
||||
const config = { protocol: 'anthropic', model }
|
||||
assert.equal(omitsTemperature(config), true, model)
|
||||
assert.deepEqual(plain(samplingOptions(config, 0.7)), {})
|
||||
}
|
||||
})
|
||||
|
||||
test('agent chat, title, and memory sampling share omitsTemperature', () => {
|
||||
if (!fs.existsSync(helperPath)) {
|
||||
assert.fail('shared helper electron/services/agentSamplingOptions.ts is required')
|
||||
}
|
||||
for (const file of [ledPath, titlePath, memoryPath]) {
|
||||
const source = fs.readFileSync(file, 'utf8')
|
||||
assert.match(source, /from '\.\/agentSamplingOptions'/)
|
||||
assert.match(source, /omitsTemperature\s*\(/)
|
||||
}
|
||||
})
|
||||
Reference in new issue
Block a user