fix: remove graph extraction budget and unify image download buttons
Cross-platform packages / Validate source (push) Canceled after 0s
Cross-platform packages / Authorize desktop packaging (push) Canceled after 0s
Cross-platform packages / linux arm64 (push) Canceled after 0s
Cross-platform packages / macos arm64 (push) Canceled after 0s
Cross-platform packages / windows arm64 (push) Canceled after 0s
Cross-platform packages / linux x64 (push) Canceled after 0s
Cross-platform packages / macos x64 (push) Canceled after 0s
Cross-platform packages / windows x64 (push) Canceled after 0s
Cross-platform packages / Publish GitHub and OSS release (push) Canceled after 0s
Cross-platform packages / Validate source (push) Canceled after 0s
Cross-platform packages / Authorize desktop packaging (push) Canceled after 0s
Cross-platform packages / linux arm64 (push) Canceled after 0s
Cross-platform packages / macos arm64 (push) Canceled after 0s
Cross-platform packages / windows arm64 (push) Canceled after 0s
Cross-platform packages / linux x64 (push) Canceled after 0s
Cross-platform packages / macos x64 (push) Canceled after 0s
Cross-platform packages / windows x64 (push) Canceled after 0s
Cross-platform packages / Publish GitHub and OSS release (push) Canceled after 0s
Graph extraction could exhaust its fixed 8192-token budget before returning structured content. OpenAI requests now omit output-token limits, while Anthropic uses the model connection limit required by its protocol. Provider limits, timeout, cancellation and bounded response handling remain. Use the shared accessible icon button for image downloads in chat and the image viewer. Add graph request regression coverage and document the limits. Release note: 知识图谱抽取不再额外限制为 8192 tokens;OpenAI 由模型服务决定输出上限,Anthropic 沿用模型连接配置。聊天和图片预览中的下载按钮统一为带提示的图标按钮。
This commit is contained in:
@@ -31,6 +31,13 @@ FastGPT/RAGFlow 完整 App 与专属配置、真实远程预检索仍待验收
|
||||
| [2026-09-13 产品验证摘要](./external-knowledge-validation-2026-09-13.json) | 生产服务与本机 HTTP MCP、Dify 桌面、凭据重开、调用计数、最终自动检查状态及远程阻断的脱敏证据 |
|
||||
| [2026-09-13 UI 与真实生成复验](./external-knowledge-follow-up-2026-09-13.json) | 后续 UI 修正、三家生产 IPC 生成、Dify 完整 App 回答与引用、视觉 fixture 及本轮请求计数 |
|
||||
|
||||
## 模型图谱抽取
|
||||
|
||||
图谱抽取不设置独立的输出 token 预算。OpenAI Chat Completions 和 Responses
|
||||
请求省略输出上限参数,由模型服务决定;Anthropic 必填的 `max_tokens` 沿用默认文本
|
||||
模型连接的最大输出配置,未配置时使用应用共享协议默认值,不再固定为 8192。
|
||||
服务端仍可能因自身限制返回长度截断;既有超时、取消、响应大小边界和失败诊断保持不变。
|
||||
|
||||
## 术语
|
||||
|
||||
- **外部实例**:一个由用户配置的 Dify、FastGPT 或 RAGFlow 服务地址及其认证信息。
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { defaultAnthropicMaximumOutputTokens } from '../../shared/contracts'
|
||||
import type {
|
||||
ResolvedRuntimeSettings,
|
||||
RuntimeSettingsStore
|
||||
@@ -14,6 +15,8 @@ function store(
|
||||
modelName: 'intranet-model',
|
||||
modelProtocol: 'anthropic-messages',
|
||||
modelAuthentication: 'none',
|
||||
modelProfiles: [],
|
||||
defaultModelProfileId: 'default-model',
|
||||
...overrides
|
||||
} as ResolvedRuntimeSettings
|
||||
return {
|
||||
@@ -29,6 +32,70 @@ function jsonResponse(payload: unknown, status = 200): Response {
|
||||
}
|
||||
|
||||
describe('createModelGraphExtractor', () => {
|
||||
it.each([
|
||||
'openai-chat-completions',
|
||||
'openai-responses'
|
||||
] as const)('does not impose an output budget for %s', async (protocol) => {
|
||||
const fetcher = vi.fn<typeof fetch>(async () =>
|
||||
jsonResponse({
|
||||
choices: [{ message: { content: '{"entities":[]}' } }],
|
||||
output_text: '{"entities":[]}'
|
||||
})
|
||||
)
|
||||
const extract = createModelGraphExtractor(
|
||||
store({
|
||||
modelProtocol: protocol,
|
||||
modelProfiles: [{
|
||||
id: 'default-model',
|
||||
name: 'Default model',
|
||||
baseUrl: 'http://localhost:8000',
|
||||
modelName: 'intranet-model',
|
||||
protocol,
|
||||
authentication: 'none',
|
||||
maximumOutputTokens: 8192
|
||||
}]
|
||||
}),
|
||||
fetcher
|
||||
)
|
||||
|
||||
await expect(extract('extract this')).resolves.toEqual({ entities: [] })
|
||||
const body = JSON.parse(String(fetcher.mock.calls[0]?.[1]?.body))
|
||||
expect(body).not.toHaveProperty('max_tokens')
|
||||
expect(body).not.toHaveProperty('max_completion_tokens')
|
||||
expect(body).not.toHaveProperty('max_output_tokens')
|
||||
})
|
||||
|
||||
it.each([undefined, 48_000])(
|
||||
'uses the Anthropic connection protocol limit (%s), not an extraction budget',
|
||||
async (maximumOutputTokens) => {
|
||||
const fetcher = vi.fn<typeof fetch>(async () =>
|
||||
jsonResponse({ content: [{ type: 'text', text: '{"entities":[]}' }] })
|
||||
)
|
||||
const profile = {
|
||||
name: 'Model',
|
||||
baseUrl: 'http://localhost:8000',
|
||||
modelName: 'intranet-model',
|
||||
protocol: 'anthropic-messages' as const,
|
||||
authentication: 'none' as const
|
||||
}
|
||||
const extract = createModelGraphExtractor(
|
||||
store({
|
||||
modelProfiles: [
|
||||
{ ...profile, id: 'other-model', maximumOutputTokens: 1024 },
|
||||
{ ...profile, id: 'default-model', maximumOutputTokens }
|
||||
]
|
||||
}),
|
||||
fetcher
|
||||
)
|
||||
|
||||
await expect(extract('extract this')).resolves.toEqual({ entities: [] })
|
||||
const body = JSON.parse(String(fetcher.mock.calls[0]?.[1]?.body))
|
||||
expect(body.max_tokens).toBe(
|
||||
maximumOutputTokens ?? defaultAnthropicMaximumOutputTokens
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
it('uses an unauthenticated Anthropic endpoint with its path and query', async () => {
|
||||
const fetcher = vi.fn(async () =>
|
||||
jsonResponse({
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { RuntimeSettingsStore } from '../runtime-settings-store'
|
||||
import { defaultAnthropicMaximumOutputTokens } from '../../shared/contracts'
|
||||
import {
|
||||
createOpenAIChatCompletionsUrl,
|
||||
createOpenAIResponsesUrl
|
||||
@@ -208,7 +209,6 @@ export function createModelGraphExtractor(
|
||||
protocol === 'openai-responses'
|
||||
? {
|
||||
model: settings.modelName,
|
||||
max_output_tokens: 8192,
|
||||
stream: false,
|
||||
instructions: system,
|
||||
input: userPrompt
|
||||
@@ -216,14 +216,17 @@ export function createModelGraphExtractor(
|
||||
: protocol === 'anthropic-messages'
|
||||
? {
|
||||
model: settings.modelName,
|
||||
max_tokens: 8192,
|
||||
// Anthropic requires this field; reuse the connection's protocol limit.
|
||||
max_tokens:
|
||||
settings.modelProfiles.find(
|
||||
(profile) => profile.id === settings.defaultModelProfileId
|
||||
)?.maximumOutputTokens ?? defaultAnthropicMaximumOutputTokens,
|
||||
stream: false,
|
||||
system,
|
||||
messages: [{ role: 'user', content: userPrompt }]
|
||||
}
|
||||
: {
|
||||
model: settings.modelName,
|
||||
max_tokens: 8192,
|
||||
stream: false,
|
||||
messages: [
|
||||
{ role: 'system', content: system },
|
||||
|
||||
@@ -11670,12 +11670,14 @@ function App(): React.JSX.Element {
|
||||
</strong>
|
||||
<div>
|
||||
<button
|
||||
className="secondary-button"
|
||||
aria-label={t("chat.images.downloadImage")}
|
||||
className="icon-button attachment-action"
|
||||
title={t("chat.images.downloadImage")}
|
||||
data-tooltip={t("chat.images.downloadImage")}
|
||||
onClick={() => downloadImage(imageViewerItem)}
|
||||
type="button"
|
||||
>
|
||||
<Download size={14} />
|
||||
{t("chat.images.downloadImage")}
|
||||
<Download size={16} aria-hidden="true" />
|
||||
</button>
|
||||
<button
|
||||
aria-label={t("chat.images.closeViewer")}
|
||||
|
||||
@@ -769,11 +769,13 @@ function ChatMessageRowView({
|
||||
aria-label={t('chat.images.downloadNamed', {
|
||||
title: attachment.name
|
||||
})}
|
||||
className="icon-button attachment-action"
|
||||
title={t('chat.images.downloadImage')}
|
||||
data-tooltip={t('chat.images.downloadImage')}
|
||||
onClick={() => onDownloadImage(imageItem)}
|
||||
type="button"
|
||||
>
|
||||
<Download size={12} />
|
||||
{t('chat.images.download')}
|
||||
<Download size={16} aria-hidden="true" />
|
||||
</button>
|
||||
</span>
|
||||
)}
|
||||
@@ -911,6 +913,9 @@ function ChatMessageRowView({
|
||||
aria-label={t('chat.images.downloadNamed', {
|
||||
title: artifact.title
|
||||
})}
|
||||
className="icon-button attachment-action"
|
||||
title={t('chat.images.downloadImage')}
|
||||
data-tooltip={t('chat.images.downloadImage')}
|
||||
onClick={() =>
|
||||
onDownloadImage({
|
||||
src: artifact.content!,
|
||||
@@ -919,8 +924,7 @@ function ChatMessageRowView({
|
||||
}
|
||||
type="button"
|
||||
>
|
||||
<Download size={12} />
|
||||
{t('chat.images.download')}
|
||||
<Download size={16} aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
</figure>
|
||||
|
||||
Reference in New Issue
Block a user