Commit a5364a45 by Archer Committed by GitHub

Update marketplace (#7097)

* fix: svg

* doc

* perf: sync website

* perf: create qs guide
parent 8784b622
name: Build fastgpt-mcp-server images name: Build fastgpt-mcp-server images
on: on:
workflow_dispatch: workflow_dispatch:
push: inputs:
paths: version:
- 'projects/mcp_server/**' description: 'Image version tag (e.g. v1.0.0)'
tags: required: true
- 'v*' type: string
jobs: jobs:
validate-version: validate-version:
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
...@@ -15,12 +15,10 @@ jobs: ...@@ -15,12 +15,10 @@ jobs:
- name: Validate release version - name: Validate release version
id: version id: version
env: env:
VERSION: ${{ github.ref_name }} VERSION: ${{ github.event.inputs.version }}
REF_TYPE: ${{ github.ref_type }}
CURRENT_REF: ${{ github.ref }}
run: | run: |
if [[ "$REF_TYPE" != "tag" || ! "$VERSION" =~ ^v ]]; then if [[ ! "$VERSION" =~ ^v ]]; then
echo "::error::Release workflow must run on a tag starting with v. Current ref: ${CURRENT_REF}" echo "::error::Image version must start with v. Current value: ${VERSION}"
exit 1 exit 1
fi fi
echo "version=${VERSION}" >> "$GITHUB_OUTPUT" echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
......
name: Build fastgpt-sso-service images name: Build fastgpt-sso-service images
on: on:
workflow_dispatch: workflow_dispatch:
push: inputs:
tags: version:
- 'v*' description: 'Image version tag (e.g. v1.0.0)'
required: true
type: string
permissions: permissions:
contents: read contents: read
...@@ -20,12 +22,10 @@ jobs: ...@@ -20,12 +22,10 @@ jobs:
- name: Validate release version - name: Validate release version
id: version id: version
env: env:
VERSION: ${{ github.ref_name }} VERSION: ${{ github.event.inputs.version }}
REF_TYPE: ${{ github.ref_type }}
CURRENT_REF: ${{ github.ref }}
run: | run: |
if [[ "$REF_TYPE" != "tag" || ! "$VERSION" =~ ^v ]]; then if [[ ! "$VERSION" =~ ^v ]]; then
echo "::error::Release workflow must run on a tag starting with v. Current ref: ${CURRENT_REF}" echo "::error::Image version must start with v. Current value: ${VERSION}"
exit 1 exit 1
fi fi
echo "version=${VERSION}" >> "$GITHUB_OUTPUT" echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
......
...@@ -6,6 +6,7 @@ import { ...@@ -6,6 +6,7 @@ import {
import json5 from 'json5'; import json5 from 'json5';
import { createLLMResponse } from '../llm/request'; import { createLLMResponse } from '../llm/request';
import { getLogger, LogCategories } from '../../../common/logger'; import { getLogger, LogCategories } from '../../../common/logger';
import { getLLMModel } from '../model';
const logger = getLogger(LogCategories.MODULE.AI.FUNCTIONS); const logger = getLogger(LogCategories.MODULE.AI.FUNCTIONS);
...@@ -22,6 +23,7 @@ export async function createQuestionGuide({ ...@@ -22,6 +23,7 @@ export async function createQuestionGuide({
inputTokens: number; inputTokens: number;
outputTokens: number; outputTokens: number;
}> { }> {
const questionGuideModel = getLLMModel(model);
const concatMessages: ChatCompletionMessageParam[] = [ const concatMessages: ChatCompletionMessageParam[] = [
...messages, ...messages,
{ {
...@@ -39,7 +41,8 @@ export async function createQuestionGuide({ ...@@ -39,7 +41,8 @@ export async function createQuestionGuide({
temperature: 0.1, temperature: 0.1,
max_tokens: 200, max_tokens: 200,
messages: concatMessages, messages: concatMessages,
stream: true stream: true,
...(questionGuideModel?.reasoning ? { reasoning_effort: 'none' as const } : {})
} }
}); });
......
...@@ -243,7 +243,8 @@ const toolChoice = async (props: ActionProps) => { ...@@ -243,7 +243,8 @@ const toolChoice = async (props: ActionProps) => {
messages: filterMessages, messages: filterMessages,
tools, tools,
tool_choice: { type: 'function', function: { name: agentFunName } }, tool_choice: { type: 'function', function: { name: agentFunName } },
toolCallMode: 'toolChoice' toolCallMode: 'toolChoice',
...(extractModel.reasoning ? { reasoning_effort: 'none' as const } : {})
} as const; } as const;
const { const {
......
...@@ -46,6 +46,23 @@ export const updateWebSyncLimit = async (teamId: string) => { ...@@ -46,6 +46,23 @@ export const updateWebSyncLimit = async (teamId: string) => {
}); });
} catch {} } catch {}
}; };
/**
* 清除团队站点同步冷却时间。
*
* 站点同步任务入队时会先写入 `limit.lastWebsiteSyncTime` 做触发频率限制;
* 如果 worker 最终没有成功同步任何页面,这次空同步不应占用团队后续手动同步机会。
*/
export const clearWebSyncLimit = async (teamId: string) => {
try {
await MongoTeam.findByIdAndUpdate(teamId, {
$unset: {
'limit.lastWebsiteSyncTime': 1
}
});
} catch {}
};
export const checkWebSyncLimit = async ({ export const checkWebSyncLimit = async ({
teamId, teamId,
limitMinutes = 0 limitMinutes = 0
......
import { beforeEach, describe, expect, it, vi } from 'vitest';
const { createLLMResponseMock, getLLMModelMock } = vi.hoisted(() => ({
createLLMResponseMock: vi.fn(),
getLLMModelMock: vi.fn()
}));
vi.mock('@fastgpt/service/core/ai/llm/request', () => ({
createLLMResponse: createLLMResponseMock
}));
vi.mock('@fastgpt/service/core/ai/model', () => ({
getLLMModel: getLLMModelMock
}));
import { createQuestionGuide } from '@fastgpt/service/core/ai/functions/createQuestionGuide';
describe('createQuestionGuide', () => {
beforeEach(() => {
vi.clearAllMocks();
createLLMResponseMock.mockResolvedValue({
answerText: '["问题 1","问题 2","问题 3"]',
usage: {
inputTokens: 10,
outputTokens: 5
}
});
});
it('forces reasoning models to disable reasoning for question guide generation', async () => {
getLLMModelMock.mockReturnValue({
model: 'deepseek-r1',
reasoning: true
});
await createQuestionGuide({
messages: [],
model: 'deepseek-r1'
});
expect(createLLMResponseMock.mock.calls[0][0].body).toMatchObject({
model: 'deepseek-r1',
reasoning_effort: 'none'
});
});
it('does not set reasoning effort for non-reasoning models', async () => {
getLLMModelMock.mockReturnValue({
model: 'gpt-4o',
reasoning: false
});
await createQuestionGuide({
messages: [],
model: 'gpt-4o'
});
expect(createLLMResponseMock.mock.calls[0][0].body).not.toHaveProperty('reasoning_effort');
});
});
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
const {
createLLMResponseMock,
filterGPTMessageByMaxContextMock,
getLLMModelMock,
formatModelChars2PointsMock
} = vi.hoisted(() => ({
createLLMResponseMock: vi.fn(),
filterGPTMessageByMaxContextMock: vi.fn(),
getLLMModelMock: vi.fn(),
formatModelChars2PointsMock: vi.fn()
}));
vi.mock('@fastgpt/service/core/ai/llm/request', () => ({
createLLMResponse: createLLMResponseMock
}));
vi.mock('@fastgpt/service/core/ai/llm/utils', () => ({
filterGPTMessageByMaxContext: filterGPTMessageByMaxContextMock
}));
vi.mock('@fastgpt/service/core/ai/model', () => ({
getLLMModel: getLLMModelMock
}));
vi.mock('@fastgpt/service/support/wallet/usage/utils', () => ({
formatModelChars2Points: formatModelChars2PointsMock
}));
import { dispatchContentExtract } from '@fastgpt/service/core/workflow/dispatch/ai/extract';
const createProps = () =>
({
runningAppInfo: {
id: 'app_1'
},
node: {
nodeId: 'extract_node',
name: '内容提取',
flowNodeType: FlowNodeTypeEnum.contentExtract
},
histories: [
{
obj: ChatRoleEnum.Human,
value: [
{
text: {
content: '历史问题'
}
}
]
}
],
externalProvider: {},
usagePush: vi.fn(),
params: {
content: '张三来自杭州',
history: 6,
model: 'deepseek-r1',
description: '提取姓名',
extractKeys: [
{
key: 'name',
desc: '姓名',
required: true
}
]
}
}) as any;
describe('dispatchContentExtract', () => {
beforeEach(() => {
vi.clearAllMocks();
filterGPTMessageByMaxContextMock.mockImplementation(async ({ messages }) => messages);
formatModelChars2PointsMock.mockReturnValue({
totalPoints: 1,
modelName: 'DeepSeek R1'
});
});
it('forces reasoning models to disable reasoning in tool choice extraction', async () => {
getLLMModelMock.mockReturnValue({
model: 'deepseek-r1',
name: 'DeepSeek R1',
maxContext: 128000,
reasoning: true,
toolChoice: true
});
createLLMResponseMock.mockResolvedValue({
answerText: '',
toolCalls: [
{
function: {
arguments: '{"name":"张三"}'
}
}
],
usage: {
inputTokens: 10,
outputTokens: 5,
usedUserOpenAIKey: false
}
});
await dispatchContentExtract(createProps());
expect(createLLMResponseMock.mock.calls[0][0].body).toMatchObject({
model: 'deepseek-r1',
reasoning_effort: 'none',
toolCallMode: 'toolChoice'
});
});
it('does not set reasoning effort in completion extraction', async () => {
getLLMModelMock.mockReturnValue({
model: 'deepseek-r1',
name: 'DeepSeek R1',
maxContext: 128000,
reasoning: true,
toolChoice: false
});
createLLMResponseMock.mockResolvedValue({
answerText: '{"name":"张三"}',
usage: {
inputTokens: 10,
outputTokens: 5,
usedUserOpenAIKey: false
}
});
await dispatchContentExtract(createProps());
expect(createLLMResponseMock.mock.calls[0][0].body).toMatchObject({
model: 'deepseek-r1'
});
expect(createLLMResponseMock.mock.calls[0][0].body).not.toHaveProperty('reasoning_effort');
});
it('does not set reasoning effort for non-reasoning extraction models', async () => {
getLLMModelMock.mockReturnValue({
model: 'gpt-4o',
name: 'GPT-4o',
maxContext: 128000,
reasoning: false,
toolChoice: false
});
createLLMResponseMock.mockResolvedValue({
answerText: '{"name":"张三"}',
usage: {
inputTokens: 10,
outputTokens: 5,
usedUserOpenAIKey: false
}
});
await dispatchContentExtract(createProps());
expect(createLLMResponseMock.mock.calls[0][0].body).not.toHaveProperty('reasoning_effort');
});
});
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { clearWebSyncLimit } from '../../../support/user/utils';
import { MongoTeam } from '../../../support/user/team/teamSchema';
vi.mock('../../../support/user/team/teamSchema', () => ({
MongoTeam: {
findByIdAndUpdate: vi.fn()
}
}));
vi.mock('../../../support/user/team/teamMemberSchema', () => ({
MongoTeamMember: {
find: vi.fn()
}
}));
describe('support user utils', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('clears website sync limit timestamp', async () => {
const teamId = 'team-id';
await clearWebSyncLimit(teamId);
expect(MongoTeam.findByIdAndUpdate).toHaveBeenCalledWith(teamId, {
$unset: {
'limit.lastWebsiteSyncTime': 1
}
});
});
});
Subproject commit ce87008bf0da48e31e1e74036d1c37e903ea64c9 Subproject commit 6e8c027b70695879580f2f52f554d2d09e55599c
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment