Compare commits

..
Author SHA1 Message Date
-LAN- 0d3aab5901 refactor(api): move TokenBufferMemory to model_runtime 2026-02-28 18:02:39 +08:00
1942 changed files with 23363 additions and 64573 deletions
-10
View File
@@ -204,16 +204,6 @@ When assigned to test a directory/path, test **ALL content** within that path:
> See [Test Structure Template](#test-structure-template) for correct import/mock patterns. > See [Test Structure Template](#test-structure-template) for correct import/mock patterns.
### `nuqs` Query State Testing (Required for URL State Hooks)
When a component or hook uses `useQueryState` / `useQueryStates`:
- ✅ Use `NuqsTestingAdapter` (prefer shared helpers in `web/test/nuqs-testing.tsx`)
- ✅ Assert URL synchronization via `onUrlUpdate` (`searchParams`, `options.history`)
- ✅ For custom parsers (`createParser`), keep `parse` and `serialize` bijective and add round-trip edge cases (`%2F`, `%25`, spaces, legacy encoded values)
- ✅ Verify default-clearing behavior (default values should be removed from URL when applicable)
- ⚠️ Only mock `nuqs` directly when URL behavior is explicitly out of scope for the test
## Core Principles ## Core Principles
### 1. AAA Pattern (Arrange-Act-Assert) ### 1. AAA Pattern (Arrange-Act-Assert)
@@ -80,9 +80,6 @@ Use this checklist when generating or reviewing tests for Dify frontend componen
- [ ] Router mocks match actual Next.js API - [ ] Router mocks match actual Next.js API
- [ ] Mocks reflect actual component conditional behavior - [ ] Mocks reflect actual component conditional behavior
- [ ] Only mock: API services, complex context providers, third-party libs - [ ] Only mock: API services, complex context providers, third-party libs
- [ ] For `nuqs` URL-state tests, wrap with `NuqsTestingAdapter` (prefer `web/test/nuqs-testing.tsx`)
- [ ] For `nuqs` URL-state tests, assert `onUrlUpdate` payload (`searchParams`, `options.history`)
- [ ] If custom `nuqs` parser exists, add round-trip tests for encoded edge cases (`%2F`, `%25`, spaces, legacy encoded values)
### Queries ### Queries
@@ -125,31 +125,6 @@ describe('Component', () => {
}) })
``` ```
### 2.1 `nuqs` Query State (Preferred: Testing Adapter)
For tests that validate URL query behavior, use `NuqsTestingAdapter` instead of mocking `nuqs` directly.
```typescript
import { renderHookWithNuqs } from '@/test/nuqs-testing'
it('should sync query to URL with push history', async () => {
const { result, onUrlUpdate } = renderHookWithNuqs(() => useMyQueryState(), {
searchParams: '?page=1',
})
act(() => {
result.current.setQuery({ page: 2 })
})
await waitFor(() => expect(onUrlUpdate).toHaveBeenCalled())
const update = onUrlUpdate.mock.calls[onUrlUpdate.mock.calls.length - 1][0]
expect(update.options.history).toBe('push')
expect(update.searchParams.get('page')).toBe('2')
})
```
Use direct `vi.mock('nuqs')` only when URL synchronization is intentionally out of scope.
### 3. Portal Components (with Shared State) ### 3. Portal Components (with Shared State)
```typescript ```typescript
+21 -78
View File
@@ -1,100 +1,43 @@
--- ---
name: orpc-contract-first name: orpc-contract-first
description: Guide for implementing oRPC contract-first API patterns in Dify frontend. Trigger when creating or updating contracts in web/contract, wiring router composition, integrating TanStack Query with typed contracts, migrating legacy service calls to oRPC, or deciding whether to call queryOptions directly vs extracting a helper or use-* hook in web/service. description: Guide for implementing oRPC contract-first API patterns in Dify frontend. Triggers when creating new API contracts, adding service endpoints, integrating TanStack Query with typed contracts, or migrating legacy service calls to oRPC. Use for all API layer work in web/contract and web/service directories.
--- ---
# oRPC Contract-First Development # oRPC Contract-First Development
## Intent ## Project Structure
- Keep contract as single source of truth in `web/contract/*`. ```
- Default query usage: call-site `useQuery(consoleQuery|marketplaceQuery.xxx.queryOptions(...))` when endpoint behavior maps 1:1 to the contract.
- Keep abstractions minimal and preserve TypeScript inference.
## Minimal Structure
```text
web/contract/ web/contract/
├── base.ts ├── base.ts # Base contract (inputStructure: 'detailed')
├── router.ts ├── router.ts # Router composition & type exports
├── marketplace.ts ├── marketplace.ts # Marketplace contracts
└── console/ └── console/ # Console contracts by domain
├── billing.ts ├── system.ts
└── ...other domains └── billing.ts
web/service/client.ts
``` ```
## Core Workflow ## Workflow
1. Define contract in `web/contract/console/{domain}.ts` or `web/contract/marketplace.ts` 1. **Create contract** in `web/contract/console/{domain}.ts`
- Use `base.route({...}).output(type<...>())` as baseline. - Import `base` from `../base` and `type` from `@orpc/contract`
- Add `.input(type<...>())` only when request has `params/query/body`. - Define route with `path`, `method`, `input`, `output`
- For `GET` without input, omit `.input(...)` (do not use `.input(type<unknown>())`).
2. Register contract in `web/contract/router.ts`
- Import directly from domain files and nest by API prefix.
3. Consume from UI call sites via oRPC query utils.
```typescript 2. **Register in router** at `web/contract/router.ts`
import { useQuery } from '@tanstack/react-query' - Import directly from domain file (no barrel files)
import { consoleQuery } from '@/service/client' - Nest by API prefix: `billing: { invoices, bindPartnerStack }`
const invoiceQuery = useQuery(consoleQuery.billing.invoices.queryOptions({ 3. **Create hooks** in `web/service/use-{domain}.ts`
staleTime: 5 * 60 * 1000, - Use `consoleQuery.{group}.{contract}.queryKey()` for query keys
throwOnError: true, - Use `consoleClient.{group}.{contract}()` for API calls
select: invoice => invoice.url,
}))
```
## Query Usage Decision Rule ## Key Rules
1. Default: call site directly uses `*.queryOptions(...)`.
2. If 3+ call sites share the same extra options (for example `retry: false`), extract a small queryOptions helper, not a `use-*` passthrough hook.
3. Create `web/service/use-{domain}.ts` only for orchestration:
- Combine multiple queries/mutations.
- Share domain-level derived state or invalidation helpers.
```typescript
const invoicesBaseQueryOptions = () =>
consoleQuery.billing.invoices.queryOptions({ retry: false })
const invoiceQuery = useQuery({
...invoicesBaseQueryOptions(),
throwOnError: true,
})
```
## Mutation Usage Decision Rule
1. Default: call mutation helpers from `consoleQuery` / `marketplaceQuery`, for example `useMutation(consoleQuery.billing.bindPartnerStack.mutationOptions(...))`.
2. If mutation flow is heavily custom, use oRPC clients as `mutationFn` (for example `consoleClient.xxx` / `marketplaceClient.xxx`), instead of generic handwritten non-oRPC mutation logic.
## Key API Guide (`.key` vs `.queryKey` vs `.mutationKey`)
- `.key(...)`:
- Use for partial matching operations (recommended for invalidation/refetch/cancel patterns).
- Example: `queryClient.invalidateQueries({ queryKey: consoleQuery.billing.key() })`
- `.queryKey(...)`:
- Use for a specific query's full key (exact query identity / direct cache addressing).
- `.mutationKey(...)`:
- Use for a specific mutation's full key.
- Typical use cases: mutation defaults registration, mutation-status filtering (`useIsMutating`, `queryClient.isMutating`), or explicit devtools grouping.
## Anti-Patterns
- Do not wrap `useQuery` with `options?: Partial<UseQueryOptions>`.
- Do not split local `queryKey/queryFn` when oRPC `queryOptions` already exists and fits the use case.
- Do not create thin `use-*` passthrough hooks for a single endpoint.
- Reason: these patterns can degrade inference (`data` may become `unknown`, especially around `throwOnError`/`select`) and add unnecessary indirection.
## Contract Rules
- **Input structure**: Always use `{ params, query?, body? }` format - **Input structure**: Always use `{ params, query?, body? }` format
- **No-input GET**: Omit `.input(...)`; do not use `.input(type<unknown>())`
- **Path params**: Use `{paramName}` in path, match in `params` object - **Path params**: Use `{paramName}` in path, match in `params` object
- **Router nesting**: Group by API prefix (e.g., `/billing/*` -> `billing: {}`) - **Router nesting**: Group by API prefix (e.g., `/billing/*` `billing: {}`)
- **No barrel files**: Import directly from specific files - **No barrel files**: Import directly from specific files
- **Types**: Import from `@/types/`, use `type<T>()` helper - **Types**: Import from `@/types/`, use `type<T>()` helper
- **Mutations**: Prefer `mutationOptions`; use explicit `mutationKey` mainly for defaults/filtering/devtools
## Type Export ## Type Export
+1 -1
View File
@@ -7,7 +7,7 @@ cd web && pnpm install
pipx install uv pipx install uv
echo "alias start-api=\"cd $WORKSPACE_ROOT/api && uv run python -m flask run --host 0.0.0.0 --port=5001 --debug\"" >> ~/.bashrc echo "alias start-api=\"cd $WORKSPACE_ROOT/api && uv run python -m flask run --host 0.0.0.0 --port=5001 --debug\"" >> ~/.bashrc
echo "alias start-worker=\"cd $WORKSPACE_ROOT/api && uv run python -m celery -A app.celery worker -P threads -c 1 --loglevel INFO -Q dataset,dataset_summary,priority_dataset,priority_pipeline,pipeline,mail,ops_trace,app_deletion,plugin,workflow_storage,conversation,workflow,schedule_poller,schedule_executor,triggered_workflow_dispatcher,trigger_refresh_executor,retention\"" >> ~/.bashrc echo "alias start-worker=\"cd $WORKSPACE_ROOT/api && uv run python -m celery -A app.celery worker -P threads -c 1 --loglevel INFO -Q dataset,priority_dataset,priority_pipeline,pipeline,mail,ops_trace,app_deletion,plugin,workflow_storage,conversation,workflow,schedule_poller,schedule_executor,triggered_workflow_dispatcher,trigger_refresh_executor,retention\"" >> ~/.bashrc
echo "alias start-web=\"cd $WORKSPACE_ROOT/web && pnpm dev:inspect\"" >> ~/.bashrc echo "alias start-web=\"cd $WORKSPACE_ROOT/web && pnpm dev:inspect\"" >> ~/.bashrc
echo "alias start-web-prod=\"cd $WORKSPACE_ROOT/web && pnpm build && pnpm start\"" >> ~/.bashrc echo "alias start-web-prod=\"cd $WORKSPACE_ROOT/web && pnpm build && pnpm start\"" >> ~/.bashrc
echo "alias start-containers=\"cd $WORKSPACE_ROOT/docker && docker-compose -f docker-compose.middleware.yaml -p dify --env-file middleware.env up -d\"" >> ~/.bashrc echo "alias start-containers=\"cd $WORKSPACE_ROOT/docker && docker-compose -f docker-compose.middleware.yaml -p dify --env-file middleware.env up -d\"" >> ~/.bashrc
+1 -1
View File
@@ -36,7 +36,7 @@
/api/core/workflow/graph/ @laipz8200 @QuantumGhost /api/core/workflow/graph/ @laipz8200 @QuantumGhost
/api/core/workflow/graph_events/ @laipz8200 @QuantumGhost /api/core/workflow/graph_events/ @laipz8200 @QuantumGhost
/api/core/workflow/node_events/ @laipz8200 @QuantumGhost /api/core/workflow/node_events/ @laipz8200 @QuantumGhost
/api/dify_graph/model_runtime/ @laipz8200 @QuantumGhost /api/core/model_runtime/ @laipz8200 @QuantumGhost
# Backend - Workflow - Nodes (Agent, Iteration, Loop, LLM) # Backend - Workflow - Nodes (Agent, Iteration, Loop, LLM)
/api/core/workflow/nodes/agent/ @Nov1c444 /api/core/workflow/nodes/agent/ @Nov1c444
+7 -25
View File
@@ -1,43 +1,25 @@
version: 2 version: 2
multi-ecosystem-groups:
python:
schedule:
interval: "weekly" # or whatever schedule you want
updates: updates:
- package-ecosystem: "pip" - package-ecosystem: "pip"
directory: "/api" directory: "/api"
open-pull-requests-limit: 2 open-pull-requests-limit: 2
patterns: ["*"]
schedule: schedule:
interval: "weekly" interval: "weekly"
groups:
python-dependencies:
patterns:
- "*"
- package-ecosystem: "uv" - package-ecosystem: "uv"
directory: "/api" directory: "/api"
open-pull-requests-limit: 2 open-pull-requests-limit: 2
patterns: ["*"]
schedule: schedule:
interval: "weekly" interval: "weekly"
groups:
uv-dependencies:
patterns:
- "*"
- package-ecosystem: "npm" - package-ecosystem: "npm"
directory: "/web" directory: "/web"
schedule: schedule:
interval: "weekly" interval: "weekly"
open-pull-requests-limit: 2 open-pull-requests-limit: 2
groups:
lexical:
patterns:
- "lexical"
- "@lexical/*"
storybook:
patterns:
- "storybook"
- "@storybook/*"
npm-dependencies:
patterns:
- "*"
exclude-patterns:
- "lexical"
- "@lexical/*"
- "storybook"
- "@storybook/*"
+3 -9
View File
@@ -29,26 +29,20 @@ jobs:
- name: Install dependencies - name: Install dependencies
run: uv sync --project api --dev run: uv sync --project api --dev
- name: Prepare diagnostics extractor
run: |
git show ${{ github.event.pull_request.head.sha }}:api/libs/pyrefly_diagnostics.py > /tmp/pyrefly_diagnostics.py
- name: Run pyrefly on PR branch - name: Run pyrefly on PR branch
run: | run: |
uv run --directory api --dev pyrefly check 2>&1 \ uv run --directory api pyrefly check > /tmp/pyrefly_pr.txt 2>&1 || true
| uv run --directory api python /tmp/pyrefly_diagnostics.py > /tmp/pyrefly_pr.txt || true
- name: Checkout base branch - name: Checkout base branch
run: git checkout ${{ github.base_ref }} run: git checkout ${{ github.base_ref }}
- name: Run pyrefly on base branch - name: Run pyrefly on base branch
run: | run: |
uv run --directory api --dev pyrefly check 2>&1 \ uv run --directory api pyrefly check > /tmp/pyrefly_base.txt 2>&1 || true
| uv run --directory api python /tmp/pyrefly_diagnostics.py > /tmp/pyrefly_base.txt || true
- name: Compute diff - name: Compute diff
run: | run: |
diff -u /tmp/pyrefly_base.txt /tmp/pyrefly_pr.txt > pyrefly_diff.txt || true diff /tmp/pyrefly_base.txt /tmp/pyrefly_pr.txt > pyrefly_diff.txt || true
- name: Save PR number - name: Save PR number
run: | run: |
+1 -1
View File
@@ -89,7 +89,7 @@ jobs:
uses: actions/setup-node@v6 uses: actions/setup-node@v6
if: steps.changed-files.outputs.any_changed == 'true' if: steps.changed-files.outputs.any_changed == 'true'
with: with:
node-version: 22 node-version: 24
cache: pnpm cache: pnpm
cache-dependency-path: ./web/pnpm-lock.yaml cache-dependency-path: ./web/pnpm-lock.yaml
+1 -1
View File
@@ -28,7 +28,7 @@ jobs:
- name: Use Node.js - name: Use Node.js
uses: actions/setup-node@v6 uses: actions/setup-node@v6
with: with:
node-version: 22 node-version: 24
cache: '' cache: ''
cache-dependency-path: 'pnpm-lock.yaml' cache-dependency-path: 'pnpm-lock.yaml'
+1 -1
View File
@@ -57,7 +57,7 @@ jobs:
- name: Set up Node.js - name: Set up Node.js
uses: actions/setup-node@v6 uses: actions/setup-node@v6
with: with:
node-version: 22 node-version: 24
cache: pnpm cache: pnpm
cache-dependency-path: ./web/pnpm-lock.yaml cache-dependency-path: ./web/pnpm-lock.yaml
+3 -3
View File
@@ -39,7 +39,7 @@ jobs:
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@v6 uses: actions/setup-node@v6
with: with:
node-version: 22 node-version: 24
cache: pnpm cache: pnpm
cache-dependency-path: ./web/pnpm-lock.yaml cache-dependency-path: ./web/pnpm-lock.yaml
@@ -83,7 +83,7 @@ jobs:
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@v6 uses: actions/setup-node@v6
with: with:
node-version: 22 node-version: 24
cache: pnpm cache: pnpm
cache-dependency-path: ./web/pnpm-lock.yaml cache-dependency-path: ./web/pnpm-lock.yaml
@@ -457,7 +457,7 @@ jobs:
uses: actions/setup-node@v6 uses: actions/setup-node@v6
if: steps.changed-files.outputs.any_changed == 'true' if: steps.changed-files.outputs.any_changed == 'true'
with: with:
node-version: 22 node-version: 24
cache: pnpm cache: pnpm
cache-dependency-path: ./web/pnpm-lock.yaml cache-dependency-path: ./web/pnpm-lock.yaml
-1
View File
@@ -222,7 +222,6 @@ mise.toml
# AI Assistant # AI Assistant
.roo/ .roo/
/.claude/worktrees/
api/.env.backup api/.env.backup
/clickzetta /clickzetta
+1 -1
View File
@@ -37,7 +37,7 @@
"-c", "-c",
"1", "1",
"-Q", "-Q",
"dataset,dataset_summary,priority_dataset,priority_pipeline,pipeline,mail,ops_trace,app_deletion,plugin,workflow_storage,conversation,workflow,schedule_poller,schedule_executor,triggered_workflow_dispatcher,trigger_refresh_executor,retention,workflow_based_app_execution", "dataset,priority_dataset,priority_pipeline,pipeline,mail,ops_trace,app_deletion,plugin,workflow_storage,conversation,workflow,schedule_poller,schedule_executor,triggered_workflow_dispatcher,trigger_refresh_executor,retention,workflow_based_app_execution",
"--loglevel", "--loglevel",
"INFO" "INFO"
], ],
+1 -1
View File
@@ -29,7 +29,7 @@ The codebase is split into:
## Language Style ## Language Style
- **Python**: Keep type hints on functions and attributes, and implement relevant special methods (e.g., `__repr__`, `__str__`). Prefer `TypedDict` over `dict` or `Mapping` for type safety and better code documentation. - **Python**: Keep type hints on functions and attributes, and implement relevant special methods (e.g., `__repr__`, `__str__`).
- **TypeScript**: Use the strict config, rely on ESLint (`pnpm lint:fix` preferred) plus `pnpm type-check:tsgo`, and avoid `any` types. - **TypeScript**: Use the strict config, rely on ESLint (`pnpm lint:fix` preferred) plus `pnpm type-check:tsgo`, and avoid `any` types.
## General Practices ## General Practices
+3 -3
View File
@@ -68,10 +68,10 @@ lint:
@echo "✅ Linting complete" @echo "✅ Linting complete"
type-check: type-check:
@echo "📝 Running type checks (basedpyright + pyrefly + mypy)..." @echo "📝 Running type checks (basedpyright + mypy + ty)..."
@./dev/basedpyright-check $(PATH_TO_CHECK) @./dev/basedpyright-check $(PATH_TO_CHECK)
@./dev/pyrefly-check-local
@uv --directory api run mypy --exclude-gitignore --exclude 'tests/' --exclude 'migrations/' --check-untyped-defs --disable-error-code=import-untyped . @uv --directory api run mypy --exclude-gitignore --exclude 'tests/' --exclude 'migrations/' --check-untyped-defs --disable-error-code=import-untyped .
@cd api && uv run ty check
@echo "✅ Type checks complete" @echo "✅ Type checks complete"
test: test:
@@ -132,7 +132,7 @@ help:
@echo " make format - Format code with ruff" @echo " make format - Format code with ruff"
@echo " make check - Check code with ruff" @echo " make check - Check code with ruff"
@echo " make lint - Format, fix, and lint code (ruff, imports, dotenv)" @echo " make lint - Format, fix, and lint code (ruff, imports, dotenv)"
@echo " make type-check - Run type checks (basedpyright, pyrefly, mypy)" @echo " make type-check - Run type checks (basedpyright, mypy, ty)"
@echo " make test - Run backend unit tests (or TARGET_TESTS=./api/tests/<target_tests>)" @echo " make test - Run backend unit tests (or TARGET_TESTS=./api/tests/<target_tests>)"
@echo "" @echo ""
@echo "Docker Build Targets:" @echo "Docker Build Targets:"
+1 -1
View File
@@ -133,7 +133,7 @@ Star Dify on GitHub and be instantly notified of new releases.
### Custom configurations ### Custom configurations
If you need to customize the configuration, please refer to the comments in our [.env.example](docker/.env.example) file and update the corresponding values in your `.env` file. Additionally, you might need to make adjustments to the `docker-compose.yaml` file itself, such as changing image versions, port mappings, or volume mounts, based on your specific deployment environment and requirements. After making any changes, please re-run `docker compose up -d`. You can find the full list of available environment variables [here](https://docs.dify.ai/getting-started/install-self-hosted/environments). If you need to customize the configuration, please refer to the comments in our [.env.example](docker/.env.example) file and update the corresponding values in your `.env` file. Additionally, you might need to make adjustments to the `docker-compose.yaml` file itself, such as changing image versions, port mappings, or volume mounts, based on your specific deployment environment and requirements. After making any changes, please re-run `docker-compose up -d`. You can find the full list of available environment variables [here](https://docs.dify.ai/getting-started/install-self-hosted/environments).
#### Customizing Suggested Questions #### Customizing Suggested Questions
-2
View File
@@ -42,8 +42,6 @@ REFRESH_TOKEN_EXPIRE_DAYS=30
# redis configuration # redis configuration
REDIS_HOST=localhost REDIS_HOST=localhost
REDIS_PORT=6379 REDIS_PORT=6379
# Optional: limit total connections in connection pool (unset for default)
# REDIS_MAX_CONNECTIONS=200
REDIS_USERNAME= REDIS_USERNAME=
REDIS_PASSWORD=difyai123456 REDIS_PASSWORD=difyai123456
REDIS_USE_SSL=false REDIS_USE_SSL=false
+250 -88
View File
@@ -1,7 +1,6 @@
[importlinter] [importlinter]
root_packages = root_packages =
core core
dify_graph
configs configs
controllers controllers
extensions extensions
@@ -22,39 +21,48 @@ layers =
runtime runtime
entities entities
containers = containers =
dify_graph core.workflow
ignore_imports = ignore_imports =
dify_graph.nodes.base.node -> dify_graph.graph_events core.workflow.nodes.base.node -> core.workflow.graph_events
dify_graph.nodes.iteration.iteration_node -> dify_graph.graph_events core.workflow.nodes.iteration.iteration_node -> core.workflow.graph_events
dify_graph.nodes.loop.loop_node -> dify_graph.graph_events core.workflow.nodes.loop.loop_node -> core.workflow.graph_events
dify_graph.nodes.iteration.iteration_node -> dify_graph.graph_engine core.workflow.nodes.iteration.iteration_node -> core.app.workflow.node_factory
dify_graph.nodes.loop.loop_node -> dify_graph.graph_engine core.workflow.nodes.loop.loop_node -> core.app.workflow.node_factory
core.workflow.nodes.iteration.iteration_node -> core.workflow.graph_engine
core.workflow.nodes.iteration.iteration_node -> core.workflow.graph
core.workflow.nodes.iteration.iteration_node -> core.workflow.graph_engine.command_channels
core.workflow.nodes.loop.loop_node -> core.workflow.graph_engine
core.workflow.nodes.loop.loop_node -> core.workflow.graph
core.workflow.nodes.loop.loop_node -> core.workflow.graph_engine.command_channels
# TODO(QuantumGhost): fix the import violation later # TODO(QuantumGhost): fix the import violation later
dify_graph.entities.pause_reason -> dify_graph.nodes.human_input.entities core.workflow.entities.pause_reason -> core.workflow.nodes.human_input.entities
[importlinter:contract:workflow-infrastructure-dependencies] [importlinter:contract:workflow-infrastructure-dependencies]
name = Workflow Infrastructure Dependencies name = Workflow Infrastructure Dependencies
type = forbidden type = forbidden
source_modules = source_modules =
dify_graph core.workflow
forbidden_modules = forbidden_modules =
extensions.ext_database extensions.ext_database
extensions.ext_redis extensions.ext_redis
allow_indirect_imports = True allow_indirect_imports = True
ignore_imports = ignore_imports =
dify_graph.nodes.agent.agent_node -> extensions.ext_database core.workflow.nodes.agent.agent_node -> extensions.ext_database
dify_graph.nodes.llm.file_saver -> extensions.ext_database core.workflow.nodes.knowledge_index.knowledge_index_node -> extensions.ext_database
dify_graph.nodes.llm.node -> extensions.ext_database core.workflow.nodes.llm.file_saver -> extensions.ext_database
dify_graph.nodes.tool.tool_node -> extensions.ext_database core.workflow.nodes.llm.llm_utils -> extensions.ext_database
dify_graph.model_runtime.model_providers.__base.ai_model -> extensions.ext_redis core.workflow.nodes.llm.node -> extensions.ext_database
dify_graph.model_runtime.model_providers.model_provider_factory -> extensions.ext_redis core.workflow.nodes.tool.tool_node -> extensions.ext_database
# TODO(QuantumGhost): use DI to avoid depending on global DB.
core.workflow.nodes.human_input.human_input_node -> extensions.ext_database
[importlinter:contract:workflow-external-imports] [importlinter:contract:workflow-external-imports]
name = Workflow External Imports name = Workflow External Imports
type = forbidden type = forbidden
source_modules = source_modules =
dify_graph core.workflow
forbidden_modules = forbidden_modules =
configs configs
controllers controllers
@@ -92,63 +100,217 @@ forbidden_modules =
core.trigger core.trigger
core.variables core.variables
ignore_imports = ignore_imports =
dify_graph.nodes.agent.agent_node -> core.model_manager core.workflow.nodes.loop.loop_node -> core.app.workflow.node_factory
dify_graph.nodes.agent.agent_node -> core.provider_manager core.workflow.workflow_entry -> core.app.workflow.layers.observability
dify_graph.nodes.agent.agent_node -> core.tools.tool_manager core.workflow.nodes.agent.agent_node -> core.model_manager
dify_graph.nodes.llm.llm_utils -> core.model_manager core.workflow.nodes.agent.agent_node -> core.provider_manager
dify_graph.nodes.llm.protocols -> core.model_manager core.workflow.nodes.agent.agent_node -> core.tools.tool_manager
dify_graph.nodes.llm.llm_utils -> dify_graph.model_runtime.model_providers.__base.large_language_model core.workflow.nodes.document_extractor.node -> core.helper.ssrf_proxy
dify_graph.nodes.llm.node -> core.tools.signature core.workflow.nodes.http_request.node -> core.tools.tool_file_manager
dify_graph.nodes.tool.tool_node -> core.callback_handler.workflow_tool_callback_handler core.workflow.nodes.iteration.iteration_node -> core.app.workflow.node_factory
dify_graph.nodes.tool.tool_node -> core.tools.tool_engine core.workflow.nodes.knowledge_index.knowledge_index_node -> core.rag.index_processor.index_processor_factory
dify_graph.nodes.tool.tool_node -> core.tools.tool_manager core.workflow.nodes.llm.llm_utils -> configs
dify_graph.nodes.agent.agent_node -> core.agent.entities core.workflow.nodes.llm.llm_utils -> core.app.entities.app_invoke_entities
dify_graph.nodes.agent.agent_node -> core.agent.plugin_entities core.workflow.nodes.llm.llm_utils -> core.model_manager
dify_graph.nodes.knowledge_retrieval.knowledge_retrieval_node -> core.app.app_config.entities core.workflow.nodes.llm.protocols -> core.model_manager
dify_graph.nodes.parameter_extractor.parameter_extractor_node -> core.prompt.advanced_prompt_transform core.workflow.nodes.llm.llm_utils -> core.model_runtime.model_providers.__base.large_language_model
dify_graph.nodes.parameter_extractor.parameter_extractor_node -> core.prompt.simple_prompt_transform core.workflow.nodes.llm.llm_utils -> models.model
dify_graph.nodes.parameter_extractor.parameter_extractor_node -> dify_graph.model_runtime.model_providers.__base.large_language_model core.workflow.nodes.llm.llm_utils -> models.provider
dify_graph.nodes.question_classifier.question_classifier_node -> core.prompt.simple_prompt_transform core.workflow.nodes.llm.llm_utils -> services.credit_pool_service
dify_graph.nodes.parameter_extractor.parameter_extractor_node -> core.model_manager core.workflow.nodes.llm.node -> core.tools.signature
dify_graph.nodes.question_classifier.question_classifier_node -> core.model_manager core.workflow.nodes.tool.tool_node -> core.callback_handler.workflow_tool_callback_handler
dify_graph.nodes.tool.tool_node -> core.tools.utils.message_transformer core.workflow.nodes.tool.tool_node -> core.tools.tool_engine
dify_graph.nodes.tool.tool_node -> models core.workflow.nodes.tool.tool_node -> core.tools.tool_manager
dify_graph.nodes.agent.agent_node -> models.model core.workflow.workflow_entry -> configs
dify_graph.nodes.llm.file_saver -> core.helper.ssrf_proxy core.workflow.workflow_entry -> models.workflow
dify_graph.nodes.llm.node -> core.helper.code_executor core.workflow.nodes.agent.agent_node -> core.agent.entities
dify_graph.nodes.llm.node -> core.llm_generator.output_parser.errors core.workflow.nodes.agent.agent_node -> core.agent.plugin_entities
dify_graph.nodes.llm.node -> core.llm_generator.output_parser.structured_output core.workflow.nodes.base.node -> core.app.entities.app_invoke_entities
dify_graph.nodes.llm.node -> core.model_manager core.workflow.nodes.human_input.human_input_node -> core.app.entities.app_invoke_entities
dify_graph.nodes.agent.entities -> core.prompt.entities.advanced_prompt_entities core.workflow.nodes.knowledge_index.knowledge_index_node -> core.app.entities.app_invoke_entities
dify_graph.nodes.llm.entities -> core.prompt.entities.advanced_prompt_entities core.workflow.nodes.knowledge_retrieval.knowledge_retrieval_node -> core.app.app_config.entities
dify_graph.nodes.llm.node -> core.prompt.entities.advanced_prompt_entities core.workflow.nodes.llm.node -> core.app.entities.app_invoke_entities
dify_graph.nodes.llm.node -> core.prompt.utils.prompt_message_util core.workflow.nodes.parameter_extractor.parameter_extractor_node -> core.app.entities.app_invoke_entities
dify_graph.nodes.parameter_extractor.entities -> core.prompt.entities.advanced_prompt_entities core.workflow.nodes.parameter_extractor.parameter_extractor_node -> core.prompt.advanced_prompt_transform
dify_graph.nodes.parameter_extractor.parameter_extractor_node -> core.prompt.entities.advanced_prompt_entities core.workflow.nodes.parameter_extractor.parameter_extractor_node -> core.prompt.simple_prompt_transform
dify_graph.nodes.parameter_extractor.parameter_extractor_node -> core.prompt.utils.prompt_message_util core.workflow.nodes.parameter_extractor.parameter_extractor_node -> core.model_runtime.model_providers.__base.large_language_model
dify_graph.nodes.question_classifier.entities -> core.prompt.entities.advanced_prompt_entities core.workflow.nodes.question_classifier.question_classifier_node -> core.app.entities.app_invoke_entities
dify_graph.nodes.question_classifier.question_classifier_node -> core.prompt.utils.prompt_message_util core.workflow.nodes.question_classifier.question_classifier_node -> core.prompt.advanced_prompt_transform
dify_graph.nodes.knowledge_index.entities -> core.rag.retrieval.retrieval_methods core.workflow.nodes.question_classifier.question_classifier_node -> core.prompt.simple_prompt_transform
dify_graph.nodes.llm.node -> models.dataset core.workflow.nodes.start.entities -> core.app.app_config.entities
dify_graph.nodes.agent.agent_node -> core.tools.utils.message_transformer core.workflow.nodes.start.start_node -> core.app.app_config.entities
dify_graph.nodes.llm.file_saver -> core.tools.signature core.workflow.workflow_entry -> core.app.apps.exc
dify_graph.nodes.llm.file_saver -> core.tools.tool_file_manager core.workflow.workflow_entry -> core.app.entities.app_invoke_entities
dify_graph.nodes.tool.tool_node -> core.tools.errors core.workflow.workflow_entry -> core.app.workflow.node_factory
dify_graph.nodes.agent.agent_node -> extensions.ext_database core.workflow.nodes.llm.llm_utils -> core.entities.provider_entities
dify_graph.nodes.llm.file_saver -> extensions.ext_database core.workflow.nodes.parameter_extractor.parameter_extractor_node -> core.model_manager
dify_graph.nodes.llm.node -> extensions.ext_database core.workflow.nodes.question_classifier.question_classifier_node -> core.model_manager
dify_graph.nodes.tool.tool_node -> extensions.ext_database core.workflow.nodes.llm.llm_utils -> core.variables.segments
dify_graph.nodes.agent.agent_node -> models core.workflow.nodes.loop.entities -> core.variables.types
dify_graph.nodes.llm.node -> models.model core.workflow.nodes.tool.tool_node -> core.tools.utils.message_transformer
dify_graph.nodes.agent.agent_node -> services core.workflow.nodes.tool.tool_node -> models
dify_graph.nodes.tool.tool_node -> services core.workflow.nodes.agent.agent_node -> models.model
dify_graph.model_runtime.model_providers.__base.ai_model -> configs core.workflow.nodes.code.code_node -> core.helper.code_executor.code_node_provider
dify_graph.model_runtime.model_providers.__base.ai_model -> extensions.ext_redis core.workflow.nodes.code.code_node -> core.helper.code_executor.javascript.javascript_code_provider
dify_graph.model_runtime.model_providers.__base.large_language_model -> configs core.workflow.nodes.code.code_node -> core.helper.code_executor.python3.python3_code_provider
dify_graph.model_runtime.model_providers.__base.text_embedding_model -> core.entities.embedding_type core.workflow.nodes.code.entities -> core.helper.code_executor.code_executor
dify_graph.model_runtime.model_providers.model_provider_factory -> configs core.workflow.nodes.http_request.executor -> core.helper.ssrf_proxy
dify_graph.model_runtime.model_providers.model_provider_factory -> extensions.ext_redis core.workflow.nodes.http_request.node -> core.helper.ssrf_proxy
dify_graph.model_runtime.model_providers.model_provider_factory -> models.provider_ids core.workflow.nodes.llm.file_saver -> core.helper.ssrf_proxy
core.workflow.nodes.llm.node -> core.helper.code_executor
core.workflow.nodes.template_transform.template_renderer -> core.helper.code_executor.code_executor
core.workflow.nodes.llm.node -> core.llm_generator.output_parser.errors
core.workflow.nodes.llm.node -> core.llm_generator.output_parser.structured_output
core.workflow.nodes.llm.node -> core.model_manager
core.workflow.nodes.agent.entities -> core.prompt.entities.advanced_prompt_entities
core.workflow.nodes.llm.entities -> core.prompt.entities.advanced_prompt_entities
core.workflow.nodes.llm.llm_utils -> core.prompt.entities.advanced_prompt_entities
core.workflow.nodes.llm.node -> core.prompt.entities.advanced_prompt_entities
core.workflow.nodes.llm.node -> core.prompt.utils.prompt_message_util
core.workflow.nodes.parameter_extractor.entities -> core.prompt.entities.advanced_prompt_entities
core.workflow.nodes.parameter_extractor.parameter_extractor_node -> core.prompt.entities.advanced_prompt_entities
core.workflow.nodes.parameter_extractor.parameter_extractor_node -> core.prompt.utils.prompt_message_util
core.workflow.nodes.question_classifier.entities -> core.prompt.entities.advanced_prompt_entities
core.workflow.nodes.question_classifier.question_classifier_node -> core.prompt.utils.prompt_message_util
core.workflow.nodes.knowledge_index.entities -> core.rag.retrieval.retrieval_methods
core.workflow.nodes.knowledge_index.knowledge_index_node -> core.rag.retrieval.retrieval_methods
core.workflow.nodes.knowledge_index.knowledge_index_node -> models.dataset
core.workflow.nodes.knowledge_index.knowledge_index_node -> services.summary_index_service
core.workflow.nodes.knowledge_index.knowledge_index_node -> tasks.generate_summary_index_task
core.workflow.nodes.knowledge_index.knowledge_index_node -> core.rag.index_processor.processor.paragraph_index_processor
core.workflow.nodes.llm.node -> models.dataset
core.workflow.nodes.agent.agent_node -> core.tools.utils.message_transformer
core.workflow.nodes.llm.file_saver -> core.tools.signature
core.workflow.nodes.llm.file_saver -> core.tools.tool_file_manager
core.workflow.nodes.tool.tool_node -> core.tools.errors
core.workflow.conversation_variable_updater -> core.variables
core.workflow.graph_engine.entities.commands -> core.variables.variables
core.workflow.nodes.agent.agent_node -> core.variables.segments
core.workflow.nodes.answer.answer_node -> core.variables
core.workflow.nodes.code.code_node -> core.variables.segments
core.workflow.nodes.code.code_node -> core.variables.types
core.workflow.nodes.code.entities -> core.variables.types
core.workflow.nodes.document_extractor.node -> core.variables
core.workflow.nodes.document_extractor.node -> core.variables.segments
core.workflow.nodes.http_request.executor -> core.variables.segments
core.workflow.nodes.http_request.node -> core.variables.segments
core.workflow.nodes.human_input.entities -> core.variables.consts
core.workflow.nodes.iteration.iteration_node -> core.variables
core.workflow.nodes.iteration.iteration_node -> core.variables.segments
core.workflow.nodes.iteration.iteration_node -> core.variables.variables
core.workflow.nodes.knowledge_retrieval.knowledge_retrieval_node -> core.variables
core.workflow.nodes.knowledge_retrieval.knowledge_retrieval_node -> core.variables.segments
core.workflow.nodes.list_operator.node -> core.variables
core.workflow.nodes.list_operator.node -> core.variables.segments
core.workflow.nodes.llm.node -> core.variables
core.workflow.nodes.loop.loop_node -> core.variables
core.workflow.nodes.parameter_extractor.entities -> core.variables.types
core.workflow.nodes.parameter_extractor.exc -> core.variables.types
core.workflow.nodes.parameter_extractor.parameter_extractor_node -> core.variables.types
core.workflow.nodes.tool.tool_node -> core.variables.segments
core.workflow.nodes.tool.tool_node -> core.variables.variables
core.workflow.nodes.trigger_webhook.node -> core.variables.types
core.workflow.nodes.trigger_webhook.node -> core.variables.variables
core.workflow.nodes.variable_aggregator.entities -> core.variables.types
core.workflow.nodes.variable_aggregator.variable_aggregator_node -> core.variables.segments
core.workflow.nodes.variable_assigner.common.helpers -> core.variables
core.workflow.nodes.variable_assigner.common.helpers -> core.variables.consts
core.workflow.nodes.variable_assigner.common.helpers -> core.variables.types
core.workflow.nodes.variable_assigner.v1.node -> core.variables
core.workflow.nodes.variable_assigner.v2.helpers -> core.variables
core.workflow.nodes.variable_assigner.v2.node -> core.variables
core.workflow.nodes.variable_assigner.v2.node -> core.variables.consts
core.workflow.runtime.graph_runtime_state_protocol -> core.variables.segments
core.workflow.runtime.read_only_wrappers -> core.variables.segments
core.workflow.runtime.variable_pool -> core.variables
core.workflow.runtime.variable_pool -> core.variables.consts
core.workflow.runtime.variable_pool -> core.variables.segments
core.workflow.runtime.variable_pool -> core.variables.variables
core.workflow.utils.condition.processor -> core.variables
core.workflow.utils.condition.processor -> core.variables.segments
core.workflow.variable_loader -> core.variables
core.workflow.variable_loader -> core.variables.consts
core.workflow.workflow_type_encoder -> core.variables
core.workflow.nodes.agent.agent_node -> extensions.ext_database
core.workflow.nodes.knowledge_index.knowledge_index_node -> extensions.ext_database
core.workflow.nodes.llm.file_saver -> extensions.ext_database
core.workflow.nodes.llm.llm_utils -> extensions.ext_database
core.workflow.nodes.llm.node -> extensions.ext_database
core.workflow.nodes.tool.tool_node -> extensions.ext_database
core.workflow.nodes.human_input.human_input_node -> extensions.ext_database
core.workflow.nodes.human_input.human_input_node -> core.repositories.human_input_repository
core.workflow.workflow_entry -> extensions.otel.runtime
core.workflow.nodes.agent.agent_node -> models
core.workflow.nodes.base.node -> models.enums
core.workflow.nodes.llm.llm_utils -> models.provider_ids
core.workflow.nodes.llm.node -> models.model
core.workflow.workflow_entry -> models.enums
core.workflow.nodes.agent.agent_node -> services
core.workflow.nodes.tool.tool_node -> services
core.workflow.nodes.agent.agent_node -> core.model_runtime.token_buffer_memory
core.workflow.nodes.llm.llm_utils -> core.model_runtime.token_buffer_memory
core.workflow.nodes.llm.node -> core.model_runtime.token_buffer_memory
core.workflow.nodes.parameter_extractor.parameter_extractor_node -> core.model_runtime.token_buffer_memory
core.workflow.nodes.question_classifier.question_classifier_node -> core.model_runtime.token_buffer_memory
[importlinter:contract:model-runtime-no-internal-imports]
name = Model Runtime Internal Imports
type = forbidden
source_modules =
core.model_runtime
forbidden_modules =
configs
controllers
extensions
models
services
tasks
core.agent
core.app
core.base
core.callback_handler
core.datasource
core.db
core.entities
core.errors
core.extension
core.external_data_tool
core.file
core.helper
core.hosting_configuration
core.indexing_runner
core.llm_generator
core.logging
core.mcp
core.memory
core.model_manager
core.moderation
core.ops
core.plugin
core.prompt
core.provider_manager
core.rag
core.repositories
core.schemas
core.tools
core.trigger
core.variables
core.workflow
ignore_imports =
core.model_runtime.model_providers.__base.ai_model -> configs
core.model_runtime.model_providers.__base.ai_model -> extensions.ext_redis
core.model_runtime.model_providers.__base.large_language_model -> configs
core.model_runtime.model_providers.__base.text_embedding_model -> core.entities.embedding_type
core.model_runtime.model_providers.model_provider_factory -> configs
core.model_runtime.model_providers.model_provider_factory -> extensions.ext_redis
core.model_runtime.model_providers.model_provider_factory -> models.provider_ids
core.model_runtime.token_buffer_memory -> core.app.app_config.features.file_upload.manager
core.model_runtime.token_buffer_memory -> core.model_manager
core.model_runtime.token_buffer_memory -> core.prompt.utils.extract_thread_messages
core.model_runtime.token_buffer_memory -> core.workflow.file.file_manager
core.model_runtime.token_buffer_memory -> extensions.ext_database
core.model_runtime.token_buffer_memory -> models.model
core.model_runtime.token_buffer_memory -> models.workflow
[importlinter:contract:rsc] [importlinter:contract:rsc]
name = RSC name = RSC
@@ -157,7 +319,7 @@ layers =
graph_engine graph_engine
response_coordinator response_coordinator
containers = containers =
dify_graph.graph_engine core.workflow.graph_engine
[importlinter:contract:worker] [importlinter:contract:worker]
name = Worker name = Worker
@@ -166,7 +328,7 @@ layers =
graph_engine graph_engine
worker worker
containers = containers =
dify_graph.graph_engine core.workflow.graph_engine
[importlinter:contract:graph-engine-architecture] [importlinter:contract:graph-engine-architecture]
name = Graph Engine Architecture name = Graph Engine Architecture
@@ -182,28 +344,28 @@ layers =
worker_management worker_management
domain domain
containers = containers =
dify_graph.graph_engine core.workflow.graph_engine
[importlinter:contract:domain-isolation] [importlinter:contract:domain-isolation]
name = Domain Model Isolation name = Domain Model Isolation
type = forbidden type = forbidden
source_modules = source_modules =
dify_graph.graph_engine.domain core.workflow.graph_engine.domain
forbidden_modules = forbidden_modules =
dify_graph.graph_engine.worker_management core.workflow.graph_engine.worker_management
dify_graph.graph_engine.command_channels core.workflow.graph_engine.command_channels
dify_graph.graph_engine.layers core.workflow.graph_engine.layers
dify_graph.graph_engine.protocols core.workflow.graph_engine.protocols
[importlinter:contract:worker-management] [importlinter:contract:worker-management]
name = Worker Management name = Worker Management
type = forbidden type = forbidden
source_modules = source_modules =
dify_graph.graph_engine.worker_management core.workflow.graph_engine.worker_management
forbidden_modules = forbidden_modules =
dify_graph.graph_engine.orchestration core.workflow.graph_engine.orchestration
dify_graph.graph_engine.command_processing core.workflow.graph_engine.command_processing
dify_graph.graph_engine.event_management core.workflow.graph_engine.event_management
[importlinter:contract:graph-traversal-components] [importlinter:contract:graph-traversal-components]
@@ -213,11 +375,11 @@ layers =
edge_processor edge_processor
skip_propagator skip_propagator
containers = containers =
dify_graph.graph_engine.graph_traversal core.workflow.graph_engine.graph_traversal
[importlinter:contract:command-channels] [importlinter:contract:command-channels]
name = Command Channels Independence name = Command Channels Independence
type = independence type = independence
modules = modules =
dify_graph.graph_engine.command_channels.in_memory_channel core.workflow.graph_engine.command_channels.in_memory_channel
dify_graph.graph_engine.command_channels.redis_channel core.workflow.graph_engine.command_channels.redis_channel
+1 -1
View File
@@ -100,7 +100,7 @@ ignore = [
"configs/*" = [ "configs/*" = [
"N802", # invalid-function-name "N802", # invalid-function-name
] ]
"dify_graph/model_runtime/callbacks/base_callback.py" = ["T201"] "core/model_runtime/callbacks/base_callback.py" = ["T201"]
"core/workflow/callbacks/workflow_logging_callback.py" = ["T201"] "core/workflow/callbacks/workflow_logging_callback.py" = ["T201"]
"libs/gmpy2_pkcs10aep_cipher.py" = [ "libs/gmpy2_pkcs10aep_cipher.py" = [
"N803", # invalid-argument-name "N803", # invalid-argument-name
-16
View File
@@ -62,22 +62,6 @@ This is the default standard for backend code in this repo. Follow it for new co
- Code should usually include type annotations that match the repos current Python version (avoid untyped public APIs and “mystery” values). - Code should usually include type annotations that match the repos current Python version (avoid untyped public APIs and “mystery” values).
- Prefer modern typing forms (e.g. `list[str]`, `dict[str, int]`) and avoid `Any` unless theres a strong reason. - Prefer modern typing forms (e.g. `list[str]`, `dict[str, int]`) and avoid `Any` unless theres a strong reason.
- For dictionary-like data with known keys and value types, prefer `TypedDict` over `dict[...]` or `Mapping[...]`.
- For optional keys in typed payloads, use `NotRequired[...]` (or `total=False` when most fields are optional).
- Keep `dict[...]` / `Mapping[...]` for truly dynamic key spaces where the key set is unknown.
```python
from datetime import datetime
from typing import NotRequired, TypedDict
class UserProfile(TypedDict):
user_id: str
email: str
created_at: datetime
nickname: NotRequired[str]
```
- For classes, declare member variables at the top of the class body (before `__init__`) so the class shape is obvious at a glance: - For classes, declare member variables at the top of the class body (before `__init__`) so the class shape is obvious at a glance:
```python ```python
+11 -154
View File
@@ -30,7 +30,6 @@ from extensions.ext_redis import redis_client
from extensions.ext_storage import storage from extensions.ext_storage import storage
from extensions.storage.opendal_storage import OpenDALStorage from extensions.storage.opendal_storage import OpenDALStorage
from extensions.storage.storage_type import StorageType from extensions.storage.storage_type import StorageType
from libs.datetime_utils import naive_utc_now
from libs.db_migration_lock import DbMigrationAutoRenewLock from libs.db_migration_lock import DbMigrationAutoRenewLock
from libs.helper import email as email_validate from libs.helper import email as email_validate
from libs.password import hash_password, password_pattern, valid_password from libs.password import hash_password, password_pattern, valid_password
@@ -2599,29 +2598,15 @@ def migrate_oss(
@click.option( @click.option(
"--start-from", "--start-from",
type=click.DateTime(formats=["%Y-%m-%d", "%Y-%m-%dT%H:%M:%S"]), type=click.DateTime(formats=["%Y-%m-%d", "%Y-%m-%dT%H:%M:%S"]),
required=False, required=True,
default=None,
help="Lower bound (inclusive) for created_at.", help="Lower bound (inclusive) for created_at.",
) )
@click.option( @click.option(
"--end-before", "--end-before",
type=click.DateTime(formats=["%Y-%m-%d", "%Y-%m-%dT%H:%M:%S"]), type=click.DateTime(formats=["%Y-%m-%d", "%Y-%m-%dT%H:%M:%S"]),
required=False, required=True,
default=None,
help="Upper bound (exclusive) for created_at.", help="Upper bound (exclusive) for created_at.",
) )
@click.option(
"--from-days-ago",
type=int,
default=None,
help="Relative lower bound in days ago (inclusive). Must be used with --before-days.",
)
@click.option(
"--before-days",
type=int,
default=None,
help="Relative upper bound in days ago (exclusive). Required for relative mode.",
)
@click.option("--batch-size", default=1000, show_default=True, help="Batch size for selecting messages.") @click.option("--batch-size", default=1000, show_default=True, help="Batch size for selecting messages.")
@click.option( @click.option(
"--graceful-period", "--graceful-period",
@@ -2633,10 +2618,8 @@ def migrate_oss(
def clean_expired_messages( def clean_expired_messages(
batch_size: int, batch_size: int,
graceful_period: int, graceful_period: int,
start_from: datetime.datetime | None, start_from: datetime.datetime,
end_before: datetime.datetime | None, end_before: datetime.datetime,
from_days_ago: int | None,
before_days: int | None,
dry_run: bool, dry_run: bool,
): ):
""" """
@@ -2647,70 +2630,18 @@ def clean_expired_messages(
start_at = time.perf_counter() start_at = time.perf_counter()
try: try:
abs_mode = start_from is not None and end_before is not None
rel_mode = before_days is not None
if abs_mode and rel_mode:
raise click.UsageError(
"Options are mutually exclusive: use either (--start-from,--end-before) "
"or (--from-days-ago,--before-days)."
)
if from_days_ago is not None and before_days is None:
raise click.UsageError("--from-days-ago must be used together with --before-days.")
if (start_from is None) ^ (end_before is None):
raise click.UsageError("Both --start-from and --end-before are required when using absolute time range.")
if not abs_mode and not rel_mode:
raise click.UsageError(
"You must provide either (--start-from,--end-before) or (--before-days [--from-days-ago])."
)
if rel_mode:
assert before_days is not None
if before_days < 0:
raise click.UsageError("--before-days must be >= 0.")
if from_days_ago is not None:
if from_days_ago < 0:
raise click.UsageError("--from-days-ago must be >= 0.")
if from_days_ago <= before_days:
raise click.UsageError("--from-days-ago must be greater than --before-days.")
# Create policy based on billing configuration # Create policy based on billing configuration
# NOTE: graceful_period will be ignored when billing is disabled. # NOTE: graceful_period will be ignored when billing is disabled.
policy = create_message_clean_policy(graceful_period_days=graceful_period) policy = create_message_clean_policy(graceful_period_days=graceful_period)
# Create and run the cleanup service # Create and run the cleanup service
if abs_mode: service = MessagesCleanService.from_time_range(
assert start_from is not None policy=policy,
assert end_before is not None start_from=start_from,
service = MessagesCleanService.from_time_range( end_before=end_before,
policy=policy, batch_size=batch_size,
start_from=start_from, dry_run=dry_run,
end_before=end_before, )
batch_size=batch_size,
dry_run=dry_run,
)
elif from_days_ago is None:
assert before_days is not None
service = MessagesCleanService.from_days(
policy=policy,
days=before_days,
batch_size=batch_size,
dry_run=dry_run,
)
else:
assert before_days is not None
assert from_days_ago is not None
now = naive_utc_now()
service = MessagesCleanService.from_time_range(
policy=policy,
start_from=now - datetime.timedelta(days=from_days_ago),
end_before=now - datetime.timedelta(days=before_days),
batch_size=batch_size,
dry_run=dry_run,
)
stats = service.run() stats = service.run()
end_at = time.perf_counter() end_at = time.perf_counter()
@@ -2737,77 +2668,3 @@ def clean_expired_messages(
raise raise
click.echo(click.style("messages cleanup completed.", fg="green")) click.echo(click.style("messages cleanup completed.", fg="green"))
@click.command("export-app-messages", help="Export messages for an app to JSONL.GZ.")
@click.option("--app-id", required=True, help="Application ID to export messages for.")
@click.option(
"--start-from",
type=click.DateTime(formats=["%Y-%m-%d", "%Y-%m-%dT%H:%M:%S"]),
default=None,
help="Optional lower bound (inclusive) for created_at.",
)
@click.option(
"--end-before",
type=click.DateTime(formats=["%Y-%m-%d", "%Y-%m-%dT%H:%M:%S"]),
required=True,
help="Upper bound (exclusive) for created_at.",
)
@click.option(
"--filename",
required=True,
help="Base filename (relative path). Do not include suffix like .jsonl.gz.",
)
@click.option("--use-cloud-storage", is_flag=True, default=False, help="Upload to cloud storage instead of local file.")
@click.option("--batch-size", default=1000, show_default=True, help="Batch size for cursor pagination.")
@click.option("--dry-run", is_flag=True, default=False, help="Scan only, print stats without writing any file.")
def export_app_messages(
app_id: str,
start_from: datetime.datetime | None,
end_before: datetime.datetime,
filename: str,
use_cloud_storage: bool,
batch_size: int,
dry_run: bool,
):
if start_from and start_from >= end_before:
raise click.UsageError("--start-from must be before --end-before.")
from services.retention.conversation.message_export_service import AppMessageExportService
try:
validated_filename = AppMessageExportService.validate_export_filename(filename)
except ValueError as e:
raise click.BadParameter(str(e), param_hint="--filename") from e
click.echo(click.style(f"export_app_messages: starting export for app {app_id}.", fg="green"))
start_at = time.perf_counter()
try:
service = AppMessageExportService(
app_id=app_id,
end_before=end_before,
filename=validated_filename,
start_from=start_from,
batch_size=batch_size,
use_cloud_storage=use_cloud_storage,
dry_run=dry_run,
)
stats = service.run()
elapsed = time.perf_counter() - start_at
click.echo(
click.style(
f"export_app_messages: completed in {elapsed:.2f}s\n"
f" - Batches: {stats.batches}\n"
f" - Total messages: {stats.total_messages}\n"
f" - Messages with feedback: {stats.messages_with_feedback}\n"
f" - Total feedbacks: {stats.total_feedbacks}",
fg="green",
)
)
except Exception as e:
elapsed = time.perf_counter() - start_at
logger.exception("export_app_messages failed")
click.echo(click.style(f"export_app_messages: failed after {elapsed:.2f}s - {e}", fg="red"))
raise
-5
View File
@@ -111,8 +111,3 @@ class RedisConfig(BaseSettings):
description="Enable client side cache in redis", description="Enable client side cache in redis",
default=False, default=False,
) )
REDIS_MAX_CONNECTIONS: PositiveInt | None = Field(
description="Maximum connections in the Redis connection pool (unset for library default)",
default=None,
)
+17 -32
View File
@@ -1,7 +1,7 @@
from typing import Literal, Protocol from typing import Literal, Protocol
from urllib.parse import quote_plus, urlunparse from urllib.parse import quote_plus, urlunparse
from pydantic import AliasChoices, Field from pydantic import Field
from pydantic_settings import BaseSettings from pydantic_settings import BaseSettings
@@ -23,56 +23,41 @@ class RedisConfigDefaultsMixin:
class RedisPubSubConfig(BaseSettings, RedisConfigDefaultsMixin): class RedisPubSubConfig(BaseSettings, RedisConfigDefaultsMixin):
""" """
Configuration settings for event transport between API and workers. Configuration settings for Redis pub/sub streaming.
Supported transports:
- pubsub: Redis PUBLISH/SUBSCRIBE (at-most-once)
- sharded: Redis 7+ Sharded Pub/Sub (at-most-once, better scaling)
- streams: Redis Streams (at-least-once, supports late subscribers)
""" """
PUBSUB_REDIS_URL: str | None = Field( PUBSUB_REDIS_URL: str | None = Field(
validation_alias=AliasChoices("EVENT_BUS_REDIS_URL", "PUBSUB_REDIS_URL"), alias="PUBSUB_REDIS_URL",
description=( description=(
"Redis connection URL for streaming events between API and celery worker; " "Redis connection URL for pub/sub streaming events between API "
"defaults to URL constructed from `REDIS_*` configurations. Also accepts ENV: EVENT_BUS_REDIS_URL." "and celery worker, defaults to url constructed from "
"`REDIS_*` configurations"
), ),
default=None, default=None,
) )
PUBSUB_REDIS_USE_CLUSTERS: bool = Field( PUBSUB_REDIS_USE_CLUSTERS: bool = Field(
validation_alias=AliasChoices("EVENT_BUS_REDIS_CLUSTERS", "PUBSUB_REDIS_USE_CLUSTERS"),
description=( description=(
"Enable Redis Cluster mode for pub/sub or streams transport. Recommended for large deployments. " "Enable Redis Cluster mode for pub/sub streaming. It's highly "
"Also accepts ENV: EVENT_BUS_REDIS_CLUSTERS." "recommended to enable this for large deployments."
), ),
default=False, default=False,
) )
PUBSUB_REDIS_CHANNEL_TYPE: Literal["pubsub", "sharded", "streams"] = Field( PUBSUB_REDIS_CHANNEL_TYPE: Literal["pubsub", "sharded"] = Field(
validation_alias=AliasChoices("EVENT_BUS_REDIS_CHANNEL_TYPE", "PUBSUB_REDIS_CHANNEL_TYPE"),
description=( description=(
"Event transport type. Options are:\n\n" "Pub/sub channel type for streaming events. "
" - pubsub: normal Pub/Sub (at-most-once)\n" "Valid options are:\n"
" - sharded: sharded Pub/Sub (at-most-once)\n" "\n"
" - streams: Redis Streams (at-least-once, recommended to avoid subscriber races)\n\n" " - pubsub: for normal Pub/Sub\n"
"Note: Before enabling 'streams' in production, estimate your expected event volume and retention needs.\n" " - sharded: for sharded Pub/Sub\n"
"Configure Redis memory limits and stream trimming appropriately (e.g., MAXLEN and key expiry) to reduce\n" "\n"
"the risk of data loss from Redis auto-eviction under memory pressure.\n" "It's highly recommended to use sharded Pub/Sub AND redis cluster "
"Also accepts ENV: EVENT_BUS_REDIS_CHANNEL_TYPE." "for large deployments."
), ),
default="pubsub", default="pubsub",
) )
PUBSUB_STREAMS_RETENTION_SECONDS: int = Field(
validation_alias=AliasChoices("EVENT_BUS_STREAMS_RETENTION_SECONDS", "PUBSUB_STREAMS_RETENTION_SECONDS"),
description=(
"When using 'streams', expire each stream key this many seconds after the last event is published. "
"Also accepts ENV: EVENT_BUS_STREAMS_RETENTION_SECONDS."
),
default=600,
)
def _build_default_pubsub_url(self) -> str: def _build_default_pubsub_url(self) -> str:
defaults = self._redis_defaults() defaults = self._redis_defaults()
if not defaults.REDIS_HOST or not defaults.REDIS_PORT: if not defaults.REDIS_HOST or not defaults.REDIS_PORT:
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -12,7 +12,7 @@ or any other web framework.
import contextvars import contextvars
from collections.abc import Callable from collections.abc import Callable
from dify_graph.context.execution_context import ( from core.workflow.context.execution_context import (
ExecutionContext, ExecutionContext,
IExecutionContext, IExecutionContext,
NullAppContext, NullAppContext,
+2 -2
View File
@@ -10,8 +10,8 @@ from typing import Any, final
from flask import Flask, current_app, g from flask import Flask, current_app, g
from dify_graph.context import register_context_capturer from core.workflow.context import register_context_capturer
from dify_graph.context.execution_context import ( from core.workflow.context.execution_context import (
AppContext, AppContext,
IExecutionContext, IExecutionContext,
) )
+1 -1
View File
@@ -4,7 +4,7 @@ from typing import Any, TypeAlias
from pydantic import BaseModel, ConfigDict, computed_field from pydantic import BaseModel, ConfigDict, computed_field
from dify_graph.file import helpers as file_helpers from core.workflow.file import helpers as file_helpers
from models.model import IconType from models.model import IconType
JSONValue: TypeAlias = str | int | float | bool | None | dict[str, Any] | list[Any] JSONValue: TypeAlias = str | int | float | bool | None | dict[str, Any] | list[Any]
+2 -2
View File
@@ -25,8 +25,8 @@ from controllers.console.wraps import (
) )
from core.ops.ops_trace_manager import OpsTraceManager from core.ops.ops_trace_manager import OpsTraceManager
from core.rag.retrieval.retrieval_methods import RetrievalMethod from core.rag.retrieval.retrieval_methods import RetrievalMethod
from dify_graph.enums import NodeType, WorkflowExecutionStatus from core.workflow.enums import NodeType, WorkflowExecutionStatus
from dify_graph.file import helpers as file_helpers from core.workflow.file import helpers as file_helpers
from extensions.ext_database import db from extensions.ext_database import db
from libs.login import current_account_with_tenant, login_required from libs.login import current_account_with_tenant, login_required
from models import App, DatasetPermissionEnum, Workflow from models import App, DatasetPermissionEnum, Workflow
+1 -1
View File
@@ -22,7 +22,7 @@ from controllers.console.app.error import (
from controllers.console.app.wraps import get_app_model from controllers.console.app.wraps import get_app_model
from controllers.console.wraps import account_initialization_required, setup_required from controllers.console.wraps import account_initialization_required, setup_required
from core.errors.error import ModelCurrentlyNotSupportError, ProviderTokenNotInitError, QuotaExceededError from core.errors.error import ModelCurrentlyNotSupportError, ProviderTokenNotInitError, QuotaExceededError
from dify_graph.model_runtime.errors.invoke import InvokeError from core.model_runtime.errors.invoke import InvokeError
from libs.login import login_required from libs.login import login_required
from models import App, AppMode from models import App, AppMode
from services.audio_service import AudioService from services.audio_service import AudioService
+1 -1
View File
@@ -26,7 +26,7 @@ from core.errors.error import (
QuotaExceededError, QuotaExceededError,
) )
from core.helper.trace_id_helper import get_external_trace_id from core.helper.trace_id_helper import get_external_trace_id
from dify_graph.model_runtime.errors.invoke import InvokeError from core.model_runtime.errors.invoke import InvokeError
from libs import helper from libs import helper
from libs.helper import uuid_value from libs.helper import uuid_value
from libs.login import current_user, login_required from libs.login import current_user, login_required
+1 -1
View File
@@ -18,7 +18,7 @@ from core.helper.code_executor.javascript.javascript_code_provider import Javasc
from core.helper.code_executor.python3.python3_code_provider import Python3CodeProvider from core.helper.code_executor.python3.python3_code_provider import Python3CodeProvider
from core.llm_generator.entities import RuleCodeGeneratePayload, RuleGeneratePayload, RuleStructuredOutputPayload from core.llm_generator.entities import RuleCodeGeneratePayload, RuleGeneratePayload, RuleStructuredOutputPayload
from core.llm_generator.llm_generator import LLMGenerator from core.llm_generator.llm_generator import LLMGenerator
from dify_graph.model_runtime.errors.invoke import InvokeError from core.model_runtime.errors.invoke import InvokeError
from extensions.ext_database import db from extensions.ext_database import db
from libs.login import current_account_with_tenant, login_required from libs.login import current_account_with_tenant, login_required
from models import App from models import App
+1 -1
View File
@@ -24,7 +24,7 @@ from controllers.console.wraps import (
) )
from core.app.entities.app_invoke_entities import InvokeFrom from core.app.entities.app_invoke_entities import InvokeFrom
from core.errors.error import ModelCurrentlyNotSupportError, ProviderTokenNotInitError, QuotaExceededError from core.errors.error import ModelCurrentlyNotSupportError, ProviderTokenNotInitError, QuotaExceededError
from dify_graph.model_runtime.errors.invoke import InvokeError from core.model_runtime.errors.invoke import InvokeError
from extensions.ext_database import db from extensions.ext_database import db
from fields.raws import FilesContainedField from fields.raws import FilesContainedField
from libs.helper import TimestampField, uuid_value from libs.helper import TimestampField, uuid_value
+4 -4
View File
@@ -21,6 +21,7 @@ from core.app.apps.base_app_queue_manager import AppQueueManager
from core.app.apps.workflow.app_generator import SKIP_PREPARE_USER_INPUTS_KEY from core.app.apps.workflow.app_generator import SKIP_PREPARE_USER_INPUTS_KEY
from core.app.entities.app_invoke_entities import InvokeFrom from core.app.entities.app_invoke_entities import InvokeFrom
from core.helper.trace_id_helper import get_external_trace_id from core.helper.trace_id_helper import get_external_trace_id
from core.model_runtime.utils.encoders import jsonable_encoder
from core.plugin.impl.exc import PluginInvokeError from core.plugin.impl.exc import PluginInvokeError
from core.trigger.debug.event_selectors import ( from core.trigger.debug.event_selectors import (
TriggerDebugEvent, TriggerDebugEvent,
@@ -28,10 +29,9 @@ from core.trigger.debug.event_selectors import (
create_event_poller, create_event_poller,
select_trigger_debug_events, select_trigger_debug_events,
) )
from dify_graph.enums import NodeType from core.workflow.enums import NodeType
from dify_graph.file.models import File from core.workflow.file.models import File
from dify_graph.graph_engine.manager import GraphEngineManager from core.workflow.graph_engine.manager import GraphEngineManager
from dify_graph.model_runtime.utils.encoders import jsonable_encoder
from extensions.ext_database import db from extensions.ext_database import db
from extensions.ext_redis import redis_client from extensions.ext_redis import redis_client
from factories import file_factory, variable_factory from factories import file_factory, variable_factory
@@ -9,7 +9,7 @@ from sqlalchemy.orm import Session
from controllers.console import console_ns from controllers.console import console_ns
from controllers.console.app.wraps import get_app_model from controllers.console.app.wraps import get_app_model
from controllers.console.wraps import account_initialization_required, setup_required from controllers.console.wraps import account_initialization_required, setup_required
from dify_graph.enums import WorkflowExecutionStatus from core.workflow.enums import WorkflowExecutionStatus
from extensions.ext_database import db from extensions.ext_database import db
from fields.workflow_app_log_fields import ( from fields.workflow_app_log_fields import (
build_workflow_app_log_pagination_model, build_workflow_app_log_pagination_model,
@@ -15,11 +15,11 @@ from controllers.console.app.error import (
from controllers.console.app.wraps import get_app_model from controllers.console.app.wraps import get_app_model
from controllers.console.wraps import account_initialization_required, edit_permission_required, setup_required from controllers.console.wraps import account_initialization_required, edit_permission_required, setup_required
from controllers.web.error import InvalidArgumentError, NotFoundError from controllers.web.error import InvalidArgumentError, NotFoundError
from dify_graph.constants import CONVERSATION_VARIABLE_NODE_ID, SYSTEM_VARIABLE_NODE_ID from core.variables.segment_group import SegmentGroup
from dify_graph.file import helpers as file_helpers from core.variables.segments import ArrayFileSegment, FileSegment, Segment
from dify_graph.variables.segment_group import SegmentGroup from core.variables.types import SegmentType
from dify_graph.variables.segments import ArrayFileSegment, FileSegment, Segment from core.workflow.constants import CONVERSATION_VARIABLE_NODE_ID, SYSTEM_VARIABLE_NODE_ID
from dify_graph.variables.types import SegmentType from core.workflow.file import helpers as file_helpers
from extensions.ext_database import db from extensions.ext_database import db
from factories.file_factory import build_from_mapping, build_from_mappings from factories.file_factory import build_from_mapping, build_from_mappings
from factories.variable_factory import build_segment_with_type from factories.variable_factory import build_segment_with_type
+2 -2
View File
@@ -12,8 +12,8 @@ from controllers.console import console_ns
from controllers.console.app.wraps import get_app_model from controllers.console.app.wraps import get_app_model
from controllers.console.wraps import account_initialization_required, setup_required from controllers.console.wraps import account_initialization_required, setup_required
from controllers.web.error import NotFoundError from controllers.web.error import NotFoundError
from dify_graph.entities.pause_reason import HumanInputRequired from core.workflow.entities.pause_reason import HumanInputRequired
from dify_graph.enums import WorkflowExecutionStatus from core.workflow.enums import WorkflowExecutionStatus
from extensions.ext_database import db from extensions.ext_database import db
from fields.end_user_fields import simple_end_user_fields from fields.end_user_fields import simple_end_user_fields
from fields.member_fields import simple_account_fields from fields.member_fields import simple_account_fields
+1 -1
View File
@@ -8,7 +8,7 @@ from pydantic import BaseModel
from werkzeug.exceptions import BadRequest, NotFound from werkzeug.exceptions import BadRequest, NotFound
from controllers.console.wraps import account_initialization_required, setup_required from controllers.console.wraps import account_initialization_required, setup_required
from dify_graph.model_runtime.utils.encoders import jsonable_encoder from core.model_runtime.utils.encoders import jsonable_encoder
from libs.login import current_account_with_tenant, login_required from libs.login import current_account_with_tenant, login_required
from models import Account from models import Account
from models.model import OAuthProviderApp from models.model import OAuthProviderApp
+4 -31
View File
@@ -25,12 +25,12 @@ from controllers.console.wraps import (
) )
from core.errors.error import LLMBadRequestError, ProviderTokenNotInitError from core.errors.error import LLMBadRequestError, ProviderTokenNotInitError
from core.indexing_runner import IndexingRunner from core.indexing_runner import IndexingRunner
from core.model_runtime.entities.model_entities import ModelType
from core.provider_manager import ProviderManager from core.provider_manager import ProviderManager
from core.rag.datasource.vdb.vector_type import VectorType from core.rag.datasource.vdb.vector_type import VectorType
from core.rag.extractor.entity.datasource_type import DatasourceType from core.rag.extractor.entity.datasource_type import DatasourceType
from core.rag.extractor.entity.extract_setting import ExtractSetting, NotionInfo, WebsiteInfo from core.rag.extractor.entity.extract_setting import ExtractSetting, NotionInfo, WebsiteInfo
from core.rag.retrieval.retrieval_methods import RetrievalMethod from core.rag.retrieval.retrieval_methods import RetrievalMethod
from dify_graph.model_runtime.entities.model_entities import ModelType
from extensions.ext_database import db from extensions.ext_database import db
from fields.app_fields import app_detail_kernel_fields, related_app_list from fields.app_fields import app_detail_kernel_fields, related_app_list
from fields.dataset_fields import ( from fields.dataset_fields import (
@@ -53,7 +53,7 @@ from fields.dataset_fields import (
from fields.document_fields import document_status_fields from fields.document_fields import document_status_fields
from libs.login import current_account_with_tenant, login_required from libs.login import current_account_with_tenant, login_required
from models import ApiToken, Dataset, Document, DocumentSegment, UploadFile from models import ApiToken, Dataset, Document, DocumentSegment, UploadFile
from models.dataset import DatasetPermission, DatasetPermissionEnum from models.dataset import DatasetPermissionEnum
from models.provider_ids import ModelProviderID from models.provider_ids import ModelProviderID
from services.api_token_service import ApiTokenCache from services.api_token_service import ApiTokenCache
from services.dataset_service import DatasetPermissionService, DatasetService, DocumentService from services.dataset_service import DatasetPermissionService, DatasetService, DocumentService
@@ -119,14 +119,6 @@ def _validate_indexing_technique(value: str | None) -> str | None:
return value return value
def _validate_doc_form(value: str | None) -> str | None:
if value is None:
return value
if value not in Dataset.DOC_FORM_LIST:
raise ValueError("Invalid doc_form.")
return value
class DatasetCreatePayload(BaseModel): class DatasetCreatePayload(BaseModel):
name: str = Field(..., min_length=1, max_length=40) name: str = Field(..., min_length=1, max_length=40)
description: str = Field("", max_length=400) description: str = Field("", max_length=400)
@@ -187,14 +179,6 @@ class IndexingEstimatePayload(BaseModel):
raise ValueError("indexing_technique is required.") raise ValueError("indexing_technique is required.")
return result return result
@field_validator("doc_form")
@classmethod
def validate_doc_form(cls, value: str) -> str:
result = _validate_doc_form(value)
if result is None:
return "text_model"
return result
class ConsoleDatasetListQuery(BaseModel): class ConsoleDatasetListQuery(BaseModel):
page: int = Field(default=1, description="Page number") page: int = Field(default=1, description="Page number")
@@ -339,18 +323,6 @@ class DatasetListApi(Resource):
model_names.append(f"{embedding_model.model}:{embedding_model.provider.provider}") model_names.append(f"{embedding_model.model}:{embedding_model.provider.provider}")
data = cast(list[dict[str, Any]], marshal(datasets, dataset_detail_fields)) data = cast(list[dict[str, Any]], marshal(datasets, dataset_detail_fields))
dataset_ids = [item["id"] for item in data if item.get("permission") == "partial_members"]
partial_members_map: dict[str, list[str]] = {}
if dataset_ids:
permissions = db.session.execute(
select(DatasetPermission.dataset_id, DatasetPermission.account_id).where(
DatasetPermission.dataset_id.in_(dataset_ids)
)
).all()
for dataset_id, account_id in permissions:
partial_members_map.setdefault(dataset_id, []).append(account_id)
for item in data: for item in data:
# convert embedding_model_provider to plugin standard format # convert embedding_model_provider to plugin standard format
if item["indexing_technique"] == "high_quality" and item["embedding_model_provider"]: if item["indexing_technique"] == "high_quality" and item["embedding_model_provider"]:
@@ -364,7 +336,8 @@ class DatasetListApi(Resource):
item["embedding_available"] = True item["embedding_available"] = True
if item.get("permission") == "partial_members": if item.get("permission") == "partial_members":
item.update({"partial_member_list": partial_members_map.get(item["id"], [])}) part_users_list = DatasetPermissionService.get_dataset_partial_member_list(item["id"])
item.update({"partial_member_list": part_users_list})
else: else:
item.update({"partial_member_list": []}) item.update({"partial_member_list": []})
@@ -24,11 +24,11 @@ from core.errors.error import (
) )
from core.indexing_runner import IndexingRunner from core.indexing_runner import IndexingRunner
from core.model_manager import ModelManager from core.model_manager import ModelManager
from core.model_runtime.entities.model_entities import ModelType
from core.model_runtime.errors.invoke import InvokeAuthorizationError
from core.plugin.impl.exc import PluginDaemonClientSideError from core.plugin.impl.exc import PluginDaemonClientSideError
from core.rag.extractor.entity.datasource_type import DatasourceType from core.rag.extractor.entity.datasource_type import DatasourceType
from core.rag.extractor.entity.extract_setting import ExtractSetting, NotionInfo, WebsiteInfo from core.rag.extractor.entity.extract_setting import ExtractSetting, NotionInfo, WebsiteInfo
from dify_graph.model_runtime.entities.model_entities import ModelType
from dify_graph.model_runtime.errors.invoke import InvokeAuthorizationError
from extensions.ext_database import db from extensions.ext_database import db
from fields.dataset_fields import dataset_fields from fields.dataset_fields import dataset_fields
from fields.document_fields import ( from fields.document_fields import (
@@ -26,7 +26,7 @@ from controllers.console.wraps import (
) )
from core.errors.error import LLMBadRequestError, ProviderTokenNotInitError from core.errors.error import LLMBadRequestError, ProviderTokenNotInitError
from core.model_manager import ModelManager from core.model_manager import ModelManager
from dify_graph.model_runtime.entities.model_entities import ModelType from core.model_runtime.entities.model_entities import ModelType
from extensions.ext_database import db from extensions.ext_database import db
from extensions.ext_redis import redis_client from extensions.ext_redis import redis_client
from fields.segment_fields import child_chunk_fields, segment_fields from fields.segment_fields import child_chunk_fields, segment_fields
@@ -19,7 +19,7 @@ from core.errors.error import (
ProviderTokenNotInitError, ProviderTokenNotInitError,
QuotaExceededError, QuotaExceededError,
) )
from dify_graph.model_runtime.errors.invoke import InvokeError from core.model_runtime.errors.invoke import InvokeError
from fields.hit_testing_fields import hit_testing_record_fields from fields.hit_testing_fields import hit_testing_record_fields
from libs.login import current_user from libs.login import current_user
from models.account import Account from models.account import Account
@@ -9,9 +9,9 @@ from configs import dify_config
from controllers.common.schema import register_schema_models from controllers.common.schema import register_schema_models
from controllers.console import console_ns from controllers.console import console_ns
from controllers.console.wraps import account_initialization_required, edit_permission_required, setup_required from controllers.console.wraps import account_initialization_required, edit_permission_required, setup_required
from core.model_runtime.errors.validate import CredentialsValidateFailedError
from core.model_runtime.utils.encoders import jsonable_encoder
from core.plugin.impl.oauth import OAuthHandler from core.plugin.impl.oauth import OAuthHandler
from dify_graph.model_runtime.errors.validate import CredentialsValidateFailedError
from dify_graph.model_runtime.utils.encoders import jsonable_encoder
from libs.login import current_account_with_tenant, login_required from libs.login import current_account_with_tenant, login_required
from models.provider_ids import DatasourceProviderID from models.provider_ids import DatasourceProviderID
from services.datasource_provider_service import DatasourceProviderService from services.datasource_provider_service import DatasourceProviderService
@@ -21,8 +21,8 @@ from controllers.console.app.workflow_draft_variable import (
from controllers.console.datasets.wraps import get_rag_pipeline from controllers.console.datasets.wraps import get_rag_pipeline
from controllers.console.wraps import account_initialization_required, setup_required from controllers.console.wraps import account_initialization_required, setup_required
from controllers.web.error import InvalidArgumentError, NotFoundError from controllers.web.error import InvalidArgumentError, NotFoundError
from dify_graph.constants import CONVERSATION_VARIABLE_NODE_ID, SYSTEM_VARIABLE_NODE_ID from core.variables.types import SegmentType
from dify_graph.variables.types import SegmentType from core.workflow.constants import CONVERSATION_VARIABLE_NODE_ID, SYSTEM_VARIABLE_NODE_ID
from extensions.ext_database import db from extensions.ext_database import db
from factories.file_factory import build_from_mapping, build_from_mappings from factories.file_factory import build_from_mapping, build_from_mappings
from factories.variable_factory import build_segment_with_type from factories.variable_factory import build_segment_with_type
@@ -33,7 +33,7 @@ from controllers.web.error import InvokeRateLimitError as InvokeRateLimitHttpErr
from core.app.apps.base_app_queue_manager import AppQueueManager from core.app.apps.base_app_queue_manager import AppQueueManager
from core.app.apps.pipeline.pipeline_generator import PipelineGenerator from core.app.apps.pipeline.pipeline_generator import PipelineGenerator
from core.app.entities.app_invoke_entities import InvokeFrom from core.app.entities.app_invoke_entities import InvokeFrom
from dify_graph.model_runtime.utils.encoders import jsonable_encoder from core.model_runtime.utils.encoders import jsonable_encoder
from extensions.ext_database import db from extensions.ext_database import db
from factories import variable_factory from factories import variable_factory
from libs import helper from libs import helper
+1 -1
View File
@@ -19,7 +19,7 @@ from controllers.console.app.error import (
) )
from controllers.console.explore.wraps import InstalledAppResource from controllers.console.explore.wraps import InstalledAppResource
from core.errors.error import ModelCurrentlyNotSupportError, ProviderTokenNotInitError, QuotaExceededError from core.errors.error import ModelCurrentlyNotSupportError, ProviderTokenNotInitError, QuotaExceededError
from dify_graph.model_runtime.errors.invoke import InvokeError from core.model_runtime.errors.invoke import InvokeError
from services.audio_service import AudioService from services.audio_service import AudioService
from services.errors.audio import ( from services.errors.audio import (
AudioTooLargeServiceError, AudioTooLargeServiceError,
@@ -24,7 +24,7 @@ from core.errors.error import (
ProviderTokenNotInitError, ProviderTokenNotInitError,
QuotaExceededError, QuotaExceededError,
) )
from dify_graph.model_runtime.errors.invoke import InvokeError from core.model_runtime.errors.invoke import InvokeError
from extensions.ext_database import db from extensions.ext_database import db
from libs import helper from libs import helper
from libs.datetime_utils import naive_utc_now from libs.datetime_utils import naive_utc_now
+1 -1
View File
@@ -21,7 +21,7 @@ from controllers.console.explore.error import (
from controllers.console.explore.wraps import InstalledAppResource from controllers.console.explore.wraps import InstalledAppResource
from core.app.entities.app_invoke_entities import InvokeFrom from core.app.entities.app_invoke_entities import InvokeFrom
from core.errors.error import ModelCurrentlyNotSupportError, ProviderTokenNotInitError, QuotaExceededError from core.errors.error import ModelCurrentlyNotSupportError, ProviderTokenNotInitError, QuotaExceededError
from dify_graph.model_runtime.errors.invoke import InvokeError from core.model_runtime.errors.invoke import InvokeError
from fields.conversation_fields import ResultResponse from fields.conversation_fields import ResultResponse
from fields.message_fields import MessageInfiniteScrollPagination, MessageListItem, SuggestedQuestionsResponse from fields.message_fields import MessageInfiniteScrollPagination, MessageListItem, SuggestedQuestionsResponse
from libs import helper from libs import helper
+2 -4
View File
@@ -1,5 +1,3 @@
from typing import Any, cast
from controllers.common import fields from controllers.common import fields
from controllers.console import console_ns from controllers.console import console_ns
from controllers.console.app.error import AppUnavailableError from controllers.console.app.error import AppUnavailableError
@@ -25,14 +23,14 @@ class AppParameterApi(InstalledAppResource):
if workflow is None: if workflow is None:
raise AppUnavailableError() raise AppUnavailableError()
features_dict: dict[str, Any] = workflow.features_dict features_dict = workflow.features_dict
user_input_form = workflow.user_input_form(to_old_structure=True) user_input_form = workflow.user_input_form(to_old_structure=True)
else: else:
app_model_config = app_model.app_model_config app_model_config = app_model.app_model_config
if app_model_config is None: if app_model_config is None:
raise AppUnavailableError() raise AppUnavailableError()
features_dict = cast(dict[str, Any], app_model_config.to_dict()) features_dict = app_model_config.to_dict()
user_input_form = features_dict.get("user_input_form", []) user_input_form = features_dict.get("user_input_form", [])
+2 -2
View File
@@ -41,8 +41,8 @@ from core.errors.error import (
ProviderTokenNotInitError, ProviderTokenNotInitError,
QuotaExceededError, QuotaExceededError,
) )
from dify_graph.graph_engine.manager import GraphEngineManager from core.model_runtime.errors.invoke import InvokeError
from dify_graph.model_runtime.errors.invoke import InvokeError from core.workflow.graph_engine.manager import GraphEngineManager
from extensions.ext_database import db from extensions.ext_database import db
from extensions.ext_redis import redis_client from extensions.ext_redis import redis_client
from fields.app_fields import ( from fields.app_fields import (
+2 -2
View File
@@ -21,8 +21,8 @@ from core.errors.error import (
ProviderTokenNotInitError, ProviderTokenNotInitError,
QuotaExceededError, QuotaExceededError,
) )
from dify_graph.graph_engine.manager import GraphEngineManager from core.model_runtime.errors.invoke import InvokeError
from dify_graph.model_runtime.errors.invoke import InvokeError from core.workflow.graph_engine.manager import GraphEngineManager
from extensions.ext_redis import redis_client from extensions.ext_redis import redis_client
from libs import helper from libs import helper
from libs.login import current_account_with_tenant from libs.login import current_account_with_tenant
+1 -1
View File
@@ -13,7 +13,7 @@ from controllers.common.errors import (
) )
from controllers.console import console_ns from controllers.console import console_ns
from core.helper import ssrf_proxy from core.helper import ssrf_proxy
from dify_graph.file import helpers as file_helpers from core.workflow.file import helpers as file_helpers
from extensions.ext_database import db from extensions.ext_database import db
from fields.file_fields import FileWithSignedUrl, RemoteFileInfo from fields.file_fields import FileWithSignedUrl, RemoteFileInfo
from libs.login import current_account_with_tenant, login_required from libs.login import current_account_with_tenant, login_required
@@ -2,7 +2,7 @@ from flask_restx import Resource, fields
from controllers.console import console_ns from controllers.console import console_ns
from controllers.console.wraps import account_initialization_required, setup_required from controllers.console.wraps import account_initialization_required, setup_required
from dify_graph.model_runtime.utils.encoders import jsonable_encoder from core.model_runtime.utils.encoders import jsonable_encoder
from libs.login import current_account_with_tenant, login_required from libs.login import current_account_with_tenant, login_required
from services.agent_service import AgentService from services.agent_service import AgentService
@@ -7,8 +7,8 @@ from pydantic import BaseModel, Field
from controllers.common.schema import register_schema_models from controllers.common.schema import register_schema_models
from controllers.console import console_ns from controllers.console import console_ns
from controllers.console.wraps import account_initialization_required, is_admin_or_owner_required, setup_required from controllers.console.wraps import account_initialization_required, is_admin_or_owner_required, setup_required
from core.model_runtime.utils.encoders import jsonable_encoder
from core.plugin.impl.exc import PluginPermissionDeniedError from core.plugin.impl.exc import PluginPermissionDeniedError
from dify_graph.model_runtime.utils.encoders import jsonable_encoder
from libs.login import current_account_with_tenant, login_required from libs.login import current_account_with_tenant, login_required
from services.plugin.endpoint_service import EndpointService from services.plugin.endpoint_service import EndpointService
@@ -5,8 +5,8 @@ from werkzeug.exceptions import Forbidden
from controllers.common.schema import register_schema_models from controllers.common.schema import register_schema_models
from controllers.console import console_ns from controllers.console import console_ns
from controllers.console.wraps import account_initialization_required, setup_required from controllers.console.wraps import account_initialization_required, setup_required
from dify_graph.model_runtime.entities.model_entities import ModelType from core.model_runtime.entities.model_entities import ModelType
from dify_graph.model_runtime.errors.validate import CredentialsValidateFailedError from core.model_runtime.errors.validate import CredentialsValidateFailedError
from libs.login import current_account_with_tenant, login_required from libs.login import current_account_with_tenant, login_required
from models import TenantAccountRole from models import TenantAccountRole
from services.model_load_balancing_service import ModelLoadBalancingService from services.model_load_balancing_service import ModelLoadBalancingService
@@ -7,9 +7,9 @@ from pydantic import BaseModel, Field, field_validator
from controllers.console import console_ns from controllers.console import console_ns
from controllers.console.wraps import account_initialization_required, is_admin_or_owner_required, setup_required from controllers.console.wraps import account_initialization_required, is_admin_or_owner_required, setup_required
from dify_graph.model_runtime.entities.model_entities import ModelType from core.model_runtime.entities.model_entities import ModelType
from dify_graph.model_runtime.errors.validate import CredentialsValidateFailedError from core.model_runtime.errors.validate import CredentialsValidateFailedError
from dify_graph.model_runtime.utils.encoders import jsonable_encoder from core.model_runtime.utils.encoders import jsonable_encoder
from libs.helper import uuid_value from libs.helper import uuid_value
from libs.login import current_account_with_tenant, login_required from libs.login import current_account_with_tenant, login_required
from services.billing_service import BillingService from services.billing_service import BillingService
+3 -3
View File
@@ -8,9 +8,9 @@ from pydantic import BaseModel, Field, field_validator
from controllers.common.schema import register_enum_models, register_schema_models from controllers.common.schema import register_enum_models, register_schema_models
from controllers.console import console_ns from controllers.console import console_ns
from controllers.console.wraps import account_initialization_required, is_admin_or_owner_required, setup_required from controllers.console.wraps import account_initialization_required, is_admin_or_owner_required, setup_required
from dify_graph.model_runtime.entities.model_entities import ModelType from core.model_runtime.entities.model_entities import ModelType
from dify_graph.model_runtime.errors.validate import CredentialsValidateFailedError from core.model_runtime.errors.validate import CredentialsValidateFailedError
from dify_graph.model_runtime.utils.encoders import jsonable_encoder from core.model_runtime.utils.encoders import jsonable_encoder
from libs.helper import uuid_value from libs.helper import uuid_value
from libs.login import current_account_with_tenant, login_required from libs.login import current_account_with_tenant, login_required
from services.model_load_balancing_service import ModelLoadBalancingService from services.model_load_balancing_service import ModelLoadBalancingService
+1 -1
View File
@@ -12,8 +12,8 @@ from controllers.common.schema import register_enum_models, register_schema_mode
from controllers.console import console_ns from controllers.console import console_ns
from controllers.console.workspace import plugin_permission_required from controllers.console.workspace import plugin_permission_required
from controllers.console.wraps import account_initialization_required, is_admin_or_owner_required, setup_required from controllers.console.wraps import account_initialization_required, is_admin_or_owner_required, setup_required
from core.model_runtime.utils.encoders import jsonable_encoder
from core.plugin.impl.exc import PluginDaemonClientSideError from core.plugin.impl.exc import PluginDaemonClientSideError
from dify_graph.model_runtime.utils.encoders import jsonable_encoder
from libs.login import current_account_with_tenant, login_required from libs.login import current_account_with_tenant, login_required
from models.account import TenantPluginAutoUpgradeStrategy, TenantPluginPermission from models.account import TenantPluginAutoUpgradeStrategy, TenantPluginPermission
from services.plugin.plugin_auto_upgrade_service import PluginAutoUpgradeService from services.plugin.plugin_auto_upgrade_service import PluginAutoUpgradeService
@@ -23,10 +23,10 @@ from core.entities.mcp_provider import MCPAuthentication, MCPConfiguration
from core.mcp.auth.auth_flow import auth, handle_callback from core.mcp.auth.auth_flow import auth, handle_callback
from core.mcp.error import MCPAuthError, MCPError, MCPRefreshTokenError from core.mcp.error import MCPAuthError, MCPError, MCPRefreshTokenError
from core.mcp.mcp_client import MCPClient from core.mcp.mcp_client import MCPClient
from core.model_runtime.utils.encoders import jsonable_encoder
from core.plugin.entities.plugin_daemon import CredentialType from core.plugin.entities.plugin_daemon import CredentialType
from core.plugin.impl.oauth import OAuthHandler from core.plugin.impl.oauth import OAuthHandler
from core.tools.entities.tool_entities import ApiProviderSchemaType, WorkflowToolParameterConfiguration from core.tools.entities.tool_entities import ApiProviderSchemaType, WorkflowToolParameterConfiguration
from dify_graph.model_runtime.utils.encoders import jsonable_encoder
from extensions.ext_database import db from extensions.ext_database import db
from libs.helper import alphanumeric, uuid_value from libs.helper import alphanumeric, uuid_value
from libs.login import current_account_with_tenant, login_required from libs.login import current_account_with_tenant, login_required
@@ -10,11 +10,11 @@ from werkzeug.exceptions import BadRequest, Forbidden
from configs import dify_config from configs import dify_config
from controllers.common.schema import register_schema_models from controllers.common.schema import register_schema_models
from controllers.web.error import NotFoundError from controllers.web.error import NotFoundError
from core.model_runtime.utils.encoders import jsonable_encoder
from core.plugin.entities.plugin_daemon import CredentialType from core.plugin.entities.plugin_daemon import CredentialType
from core.plugin.impl.oauth import OAuthHandler from core.plugin.impl.oauth import OAuthHandler
from core.trigger.entities.entities import SubscriptionBuilderUpdater from core.trigger.entities.entities import SubscriptionBuilderUpdater
from core.trigger.trigger_manager import TriggerManager from core.trigger.trigger_manager import TriggerManager
from dify_graph.model_runtime.utils.encoders import jsonable_encoder
from extensions.ext_database import db from extensions.ext_database import db
from libs.login import current_user, login_required from libs.login import current_user, login_required
from models.account import Account from models.account import Account
+4 -4
View File
@@ -36,9 +36,9 @@ ERROR_MSG_INVALID_ENCRYPTED_DATA = "Invalid encrypted data"
ERROR_MSG_INVALID_ENCRYPTED_CODE = "Invalid encrypted code" ERROR_MSG_INVALID_ENCRYPTED_CODE = "Invalid encrypted code"
def account_initialization_required(view: Callable[P, R]) -> Callable[P, R]: def account_initialization_required(view: Callable[P, R]):
@wraps(view) @wraps(view)
def decorated(*args: P.args, **kwargs: P.kwargs) -> R: def decorated(*args: P.args, **kwargs: P.kwargs):
# check account initialization # check account initialization
current_user, _ = current_account_with_tenant() current_user, _ = current_account_with_tenant()
if current_user.status == AccountStatus.UNINITIALIZED: if current_user.status == AccountStatus.UNINITIALIZED:
@@ -214,9 +214,9 @@ def cloud_utm_record(view: Callable[P, R]):
return decorated return decorated
def setup_required(view: Callable[P, R]) -> Callable[P, R]: def setup_required(view: Callable[P, R]):
@wraps(view) @wraps(view)
def decorated(*args: P.args, **kwargs: P.kwargs) -> R: def decorated(*args: P.args, **kwargs: P.kwargs):
# check setup # check setup
if ( if (
dify_config.EDITION == "SELF_HOSTED" dify_config.EDITION == "SELF_HOSTED"
+1 -1
View File
@@ -8,7 +8,7 @@ from werkzeug.exceptions import Forbidden
import services import services
from core.tools.tool_file_manager import ToolFileManager from core.tools.tool_file_manager import ToolFileManager
from dify_graph.file.helpers import verify_plugin_file_signature from core.workflow.file.helpers import verify_plugin_file_signature
from fields.file_fields import FileResponse from fields.file_fields import FileResponse
from ..common.errors import ( from ..common.errors import (
+2 -2
View File
@@ -4,6 +4,7 @@ from controllers.console.wraps import setup_required
from controllers.inner_api import inner_api_ns from controllers.inner_api import inner_api_ns
from controllers.inner_api.plugin.wraps import get_user_tenant, plugin_data from controllers.inner_api.plugin.wraps import get_user_tenant, plugin_data
from controllers.inner_api.wraps import plugin_inner_api_only from controllers.inner_api.wraps import plugin_inner_api_only
from core.model_runtime.utils.encoders import jsonable_encoder
from core.plugin.backwards_invocation.app import PluginAppBackwardsInvocation from core.plugin.backwards_invocation.app import PluginAppBackwardsInvocation
from core.plugin.backwards_invocation.base import BaseBackwardsInvocationResponse from core.plugin.backwards_invocation.base import BaseBackwardsInvocationResponse
from core.plugin.backwards_invocation.encrypt import PluginEncrypter from core.plugin.backwards_invocation.encrypt import PluginEncrypter
@@ -28,8 +29,7 @@ from core.plugin.entities.request import (
RequestRequestUploadFile, RequestRequestUploadFile,
) )
from core.tools.entities.tool_entities import ToolProviderType from core.tools.entities.tool_entities import ToolProviderType
from dify_graph.file.helpers import get_signed_file_url_for_plugin from core.workflow.file.helpers import get_signed_file_url_for_plugin
from dify_graph.model_runtime.utils.encoders import jsonable_encoder
from libs.helper import length_prefixed_response from libs.helper import length_prefixed_response
from models import Account, Tenant from models import Account, Tenant
from models.model import EndUser from models.model import EndUser
+1 -1
View File
@@ -8,9 +8,9 @@ from sqlalchemy.orm import Session
from controllers.common.schema import register_schema_model from controllers.common.schema import register_schema_model
from controllers.console.app.mcp_server import AppMCPServerStatus from controllers.console.app.mcp_server import AppMCPServerStatus
from controllers.mcp import mcp_ns from controllers.mcp import mcp_ns
from core.app.app_config.entities import VariableEntity
from core.mcp import types as mcp_types from core.mcp import types as mcp_types
from core.mcp.server.streamable_http import handle_mcp_request from core.mcp.server.streamable_http import handle_mcp_request
from dify_graph.variables.input_entities import VariableEntity
from extensions.ext_database import db from extensions.ext_database import db
from libs import helper from libs import helper
from models.model import App, AppMCPServer, AppMode, EndUser from models.model import App, AppMCPServer, AppMode, EndUser
@@ -185,4 +185,4 @@ class AnnotationUpdateDeleteApi(Resource):
def delete(self, app_model: App, annotation_id: str): def delete(self, app_model: App, annotation_id: str):
"""Delete an annotation.""" """Delete an annotation."""
AppAnnotationService.delete_app_annotation(app_model.id, annotation_id) AppAnnotationService.delete_app_annotation(app_model.id, annotation_id)
return "", 204 return {"result": "success"}, 204
+2 -4
View File
@@ -1,5 +1,3 @@
from typing import Any, cast
from flask_restx import Resource from flask_restx import Resource
from controllers.common.fields import Parameters from controllers.common.fields import Parameters
@@ -35,14 +33,14 @@ class AppParameterApi(Resource):
if workflow is None: if workflow is None:
raise AppUnavailableError() raise AppUnavailableError()
features_dict: dict[str, Any] = workflow.features_dict features_dict = workflow.features_dict
user_input_form = workflow.user_input_form(to_old_structure=True) user_input_form = workflow.user_input_form(to_old_structure=True)
else: else:
app_model_config = app_model.app_model_config app_model_config = app_model.app_model_config
if app_model_config is None: if app_model_config is None:
raise AppUnavailableError() raise AppUnavailableError()
features_dict = cast(dict[str, Any], app_model_config.to_dict()) features_dict = app_model_config.to_dict()
user_input_form = features_dict.get("user_input_form", []) user_input_form = features_dict.get("user_input_form", [])
+1 -1
View File
@@ -21,7 +21,7 @@ from controllers.service_api.app.error import (
) )
from controllers.service_api.wraps import FetchUserArg, WhereisUserArg, validate_app_token from controllers.service_api.wraps import FetchUserArg, WhereisUserArg, validate_app_token
from core.errors.error import ModelCurrentlyNotSupportError, ProviderTokenNotInitError, QuotaExceededError from core.errors.error import ModelCurrentlyNotSupportError, ProviderTokenNotInitError, QuotaExceededError
from dify_graph.model_runtime.errors.invoke import InvokeError from core.model_runtime.errors.invoke import InvokeError
from models.model import App, EndUser from models.model import App, EndUser
from services.audio_service import AudioService from services.audio_service import AudioService
from services.errors.audio import ( from services.errors.audio import (
@@ -28,7 +28,7 @@ from core.errors.error import (
QuotaExceededError, QuotaExceededError,
) )
from core.helper.trace_id_helper import get_external_trace_id from core.helper.trace_id_helper import get_external_trace_id
from dify_graph.model_runtime.errors.invoke import InvokeError from core.model_runtime.errors.invoke import InvokeError
from libs import helper from libs import helper
from libs.helper import UUIDStrOrEmpty from libs.helper import UUIDStrOrEmpty
from models.model import App, AppMode, EndUser from models.model import App, AppMode, EndUser
@@ -14,6 +14,7 @@ from controllers.service_api.wraps import FetchUserArg, WhereisUserArg, validate
from core.app.entities.app_invoke_entities import InvokeFrom from core.app.entities.app_invoke_entities import InvokeFrom
from extensions.ext_database import db from extensions.ext_database import db
from fields.conversation_fields import ( from fields.conversation_fields import (
ConversationDelete,
ConversationInfiniteScrollPagination, ConversationInfiniteScrollPagination,
SimpleConversation, SimpleConversation,
) )
@@ -162,7 +163,7 @@ class ConversationDetailApi(Resource):
ConversationService.delete(app_model, conversation_id, end_user) ConversationService.delete(app_model, conversation_id, end_user)
except services.errors.conversation.ConversationNotExistsError: except services.errors.conversation.ConversationNotExistsError:
raise NotFound("Conversation Not Exists.") raise NotFound("Conversation Not Exists.")
return "", 204 return ConversationDelete(result="success").model_dump(mode="json"), 204
@service_api_ns.route("/conversations/<uuid:c_id>/name") @service_api_ns.route("/conversations/<uuid:c_id>/name")
+3 -5
View File
@@ -27,9 +27,9 @@ from core.errors.error import (
QuotaExceededError, QuotaExceededError,
) )
from core.helper.trace_id_helper import get_external_trace_id from core.helper.trace_id_helper import get_external_trace_id
from dify_graph.enums import WorkflowExecutionStatus from core.model_runtime.errors.invoke import InvokeError
from dify_graph.graph_engine.manager import GraphEngineManager from core.workflow.enums import WorkflowExecutionStatus
from dify_graph.model_runtime.errors.invoke import InvokeError from core.workflow.graph_engine.manager import GraphEngineManager
from extensions.ext_database import db from extensions.ext_database import db
from extensions.ext_redis import redis_client from extensions.ext_redis import redis_client
from fields.workflow_app_log_fields import build_workflow_app_log_pagination_model from fields.workflow_app_log_fields import build_workflow_app_log_pagination_model
@@ -132,8 +132,6 @@ class WorkflowRunDetailApi(Resource):
app_id=app_model.id, app_id=app_model.id,
run_id=workflow_run_id, run_id=workflow_run_id,
) )
if not workflow_run:
raise NotFound("Workflow run not found.")
return workflow_run return workflow_run
@@ -14,8 +14,8 @@ from controllers.service_api.wraps import (
DatasetApiResource, DatasetApiResource,
cloud_edition_billing_rate_limit_check, cloud_edition_billing_rate_limit_check,
) )
from core.model_runtime.entities.model_entities import ModelType
from core.provider_manager import ProviderManager from core.provider_manager import ProviderManager
from dify_graph.model_runtime.entities.model_entities import ModelType
from fields.dataset_fields import dataset_detail_fields from fields.dataset_fields import dataset_detail_fields
from fields.tag_fields import DataSetTag from fields.tag_fields import DataSetTag
from libs.login import current_user from libs.login import current_user
@@ -4,7 +4,7 @@ from uuid import UUID
from flask import request from flask import request
from flask_restx import marshal from flask_restx import marshal
from pydantic import BaseModel, Field, field_validator, model_validator from pydantic import BaseModel, Field, model_validator
from sqlalchemy import desc, select from sqlalchemy import desc, select
from werkzeug.exceptions import Forbidden, NotFound from werkzeug.exceptions import Forbidden, NotFound
@@ -60,13 +60,6 @@ class DocumentTextCreatePayload(BaseModel):
embedding_model: str | None = None embedding_model: str | None = None
embedding_model_provider: str | None = None embedding_model_provider: str | None = None
@field_validator("doc_form")
@classmethod
def validate_doc_form(cls, value: str) -> str:
if value not in Dataset.DOC_FORM_LIST:
raise ValueError("Invalid doc_form.")
return value
DEFAULT_REF_TEMPLATE_SWAGGER_2_0 = "#/definitions/{model}" DEFAULT_REF_TEMPLATE_SWAGGER_2_0 = "#/definitions/{model}"
@@ -79,13 +72,6 @@ class DocumentTextUpdate(BaseModel):
doc_language: str = "English" doc_language: str = "English"
retrieval_model: RetrievalModel | None = None retrieval_model: RetrievalModel | None = None
@field_validator("doc_form")
@classmethod
def validate_doc_form(cls, value: str) -> str:
if value not in Dataset.DOC_FORM_LIST:
raise ValueError("Invalid doc_form.")
return value
@model_validator(mode="after") @model_validator(mode="after")
def check_text_and_name(self) -> Self: def check_text_and_name(self) -> Self:
if self.text is not None and self.name is None: if self.text is not None and self.name is None:
@@ -17,7 +17,7 @@ from controllers.service_api.wraps import (
) )
from core.errors.error import LLMBadRequestError, ProviderTokenNotInitError from core.errors.error import LLMBadRequestError, ProviderTokenNotInitError
from core.model_manager import ModelManager from core.model_manager import ModelManager
from dify_graph.model_runtime.entities.model_entities import ModelType from core.model_runtime.entities.model_entities import ModelType
from extensions.ext_database import db from extensions.ext_database import db
from fields.segment_fields import child_chunk_fields, segment_fields from fields.segment_fields import child_chunk_fields, segment_fields
from libs.login import current_account_with_tenant from libs.login import current_account_with_tenant
@@ -3,7 +3,7 @@ from flask_restx import Resource
from controllers.service_api import service_api_ns from controllers.service_api import service_api_ns
from controllers.service_api.wraps import validate_dataset_token from controllers.service_api.wraps import validate_dataset_token
from dify_graph.model_runtime.utils.encoders import jsonable_encoder from core.model_runtime.utils.encoders import jsonable_encoder
from services.model_provider_service import ModelProviderService from services.model_provider_service import ModelProviderService
+2 -3
View File
@@ -1,5 +1,4 @@
import logging import logging
from typing import Any, cast
from flask import request from flask import request
from flask_restx import Resource from flask_restx import Resource
@@ -58,14 +57,14 @@ class AppParameterApi(WebApiResource):
if workflow is None: if workflow is None:
raise AppUnavailableError() raise AppUnavailableError()
features_dict: dict[str, Any] = workflow.features_dict features_dict = workflow.features_dict
user_input_form = workflow.user_input_form(to_old_structure=True) user_input_form = workflow.user_input_form(to_old_structure=True)
else: else:
app_model_config = app_model.app_model_config app_model_config = app_model.app_model_config
if app_model_config is None: if app_model_config is None:
raise AppUnavailableError() raise AppUnavailableError()
features_dict = cast(dict[str, Any], app_model_config.to_dict()) features_dict = app_model_config.to_dict()
user_input_form = features_dict.get("user_input_form", []) user_input_form = features_dict.get("user_input_form", [])
+1 -1
View File
@@ -20,7 +20,7 @@ from controllers.web.error import (
) )
from controllers.web.wraps import WebApiResource from controllers.web.wraps import WebApiResource
from core.errors.error import ModelCurrentlyNotSupportError, ProviderTokenNotInitError, QuotaExceededError from core.errors.error import ModelCurrentlyNotSupportError, ProviderTokenNotInitError, QuotaExceededError
from dify_graph.model_runtime.errors.invoke import InvokeError from core.model_runtime.errors.invoke import InvokeError
from libs.helper import uuid_value from libs.helper import uuid_value
from models.model import App from models.model import App
from services.audio_service import AudioService from services.audio_service import AudioService
+1 -1
View File
@@ -25,7 +25,7 @@ from core.errors.error import (
ProviderTokenNotInitError, ProviderTokenNotInitError,
QuotaExceededError, QuotaExceededError,
) )
from dify_graph.model_runtime.errors.invoke import InvokeError from core.model_runtime.errors.invoke import InvokeError
from libs import helper from libs import helper
from libs.helper import uuid_value from libs.helper import uuid_value
from models.model import AppMode from models.model import AppMode
+2 -2
View File
@@ -20,7 +20,7 @@ from controllers.web.error import (
from controllers.web.wraps import WebApiResource from controllers.web.wraps import WebApiResource
from core.app.entities.app_invoke_entities import InvokeFrom from core.app.entities.app_invoke_entities import InvokeFrom
from core.errors.error import ModelCurrentlyNotSupportError, ProviderTokenNotInitError, QuotaExceededError from core.errors.error import ModelCurrentlyNotSupportError, ProviderTokenNotInitError, QuotaExceededError
from dify_graph.model_runtime.errors.invoke import InvokeError from core.model_runtime.errors.invoke import InvokeError
from fields.conversation_fields import ResultResponse from fields.conversation_fields import ResultResponse
from fields.message_fields import SuggestedQuestionsResponse, WebMessageInfiniteScrollPagination, WebMessageListItem from fields.message_fields import SuggestedQuestionsResponse, WebMessageInfiniteScrollPagination, WebMessageListItem
from libs import helper from libs import helper
@@ -239,7 +239,7 @@ class MessageSuggestedQuestionApi(WebApiResource):
def get(self, app_model, end_user, message_id): def get(self, app_model, end_user, message_id):
app_mode = AppMode.value_of(app_model.mode) app_mode = AppMode.value_of(app_model.mode)
if app_mode not in {AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT}: if app_mode not in {AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT}:
raise NotChatAppError() raise NotCompletionAppError()
message_id = str(message_id) message_id = str(message_id)
+1 -1
View File
@@ -11,7 +11,7 @@ from controllers.common.errors import (
UnsupportedFileTypeError, UnsupportedFileTypeError,
) )
from core.helper import ssrf_proxy from core.helper import ssrf_proxy
from dify_graph.file import helpers as file_helpers from core.workflow.file import helpers as file_helpers
from extensions.ext_database import db from extensions.ext_database import db
from fields.file_fields import FileWithSignedUrl, RemoteFileInfo from fields.file_fields import FileWithSignedUrl, RemoteFileInfo
from services.file_service import FileService from services.file_service import FileService
+2 -2
View File
@@ -22,8 +22,8 @@ from core.errors.error import (
ProviderTokenNotInitError, ProviderTokenNotInitError,
QuotaExceededError, QuotaExceededError,
) )
from dify_graph.graph_engine.manager import GraphEngineManager from core.model_runtime.errors.invoke import InvokeError
from dify_graph.model_runtime.errors.invoke import InvokeError from core.workflow.graph_engine.manager import GraphEngineManager
from extensions.ext_redis import redis_client from extensions.ext_redis import redis_client
from libs import helper from libs import helper
from models.model import App, AppMode, EndUser from models.model import App, AppMode, EndUser
+13 -13
View File
@@ -17,17 +17,8 @@ from core.app.entities.app_invoke_entities import (
) )
from core.callback_handler.agent_tool_callback_handler import DifyAgentCallbackHandler from core.callback_handler.agent_tool_callback_handler import DifyAgentCallbackHandler
from core.callback_handler.index_tool_callback_handler import DatasetIndexToolCallbackHandler from core.callback_handler.index_tool_callback_handler import DatasetIndexToolCallbackHandler
from core.memory.token_buffer_memory import TokenBufferMemory
from core.model_manager import ModelInstance from core.model_manager import ModelInstance
from core.prompt.utils.extract_thread_messages import extract_thread_messages from core.model_runtime.entities import (
from core.tools.__base.tool import Tool
from core.tools.entities.tool_entities import (
ToolParameter,
)
from core.tools.tool_manager import ToolManager
from core.tools.utils.dataset_retriever_tool import DatasetRetrieverTool
from dify_graph.file import file_manager
from dify_graph.model_runtime.entities import (
AssistantPromptMessage, AssistantPromptMessage,
LLMUsage, LLMUsage,
PromptMessage, PromptMessage,
@@ -37,9 +28,18 @@ from dify_graph.model_runtime.entities import (
ToolPromptMessage, ToolPromptMessage,
UserPromptMessage, UserPromptMessage,
) )
from dify_graph.model_runtime.entities.message_entities import ImagePromptMessageContent, PromptMessageContentUnionTypes from core.model_runtime.entities.message_entities import ImagePromptMessageContent, PromptMessageContentUnionTypes
from dify_graph.model_runtime.entities.model_entities import ModelFeature from core.model_runtime.entities.model_entities import ModelFeature
from dify_graph.model_runtime.model_providers.__base.large_language_model import LargeLanguageModel from core.model_runtime.model_providers.__base.large_language_model import LargeLanguageModel
from core.model_runtime.token_buffer_memory import TokenBufferMemory
from core.prompt.utils.extract_thread_messages import extract_thread_messages
from core.tools.__base.tool import Tool
from core.tools.entities.tool_entities import (
ToolParameter,
)
from core.tools.tool_manager import ToolManager
from core.tools.utils.dataset_retriever_tool import DatasetRetrieverTool
from core.workflow.file import file_manager
from extensions.ext_database import db from extensions.ext_database import db
from factories import file_factory from factories import file_factory
from models.enums import CreatorUserRole from models.enums import CreatorUserRole
+8 -8
View File
@@ -9,20 +9,20 @@ from core.agent.entities import AgentScratchpadUnit
from core.agent.output_parser.cot_output_parser import CotAgentOutputParser from core.agent.output_parser.cot_output_parser import CotAgentOutputParser
from core.app.apps.base_app_queue_manager import PublishFrom from core.app.apps.base_app_queue_manager import PublishFrom
from core.app.entities.queue_entities import QueueAgentThoughtEvent, QueueMessageEndEvent, QueueMessageFileEvent from core.app.entities.queue_entities import QueueAgentThoughtEvent, QueueMessageEndEvent, QueueMessageFileEvent
from core.ops.ops_trace_manager import TraceQueueManager from core.model_runtime.entities.llm_entities import LLMResult, LLMResultChunk, LLMResultChunkDelta, LLMUsage
from core.prompt.agent_history_prompt_transform import AgentHistoryPromptTransform from core.model_runtime.entities.message_entities import (
from core.tools.__base.tool import Tool
from core.tools.entities.tool_entities import ToolInvokeMeta
from core.tools.tool_engine import ToolEngine
from dify_graph.model_runtime.entities.llm_entities import LLMResult, LLMResultChunk, LLMResultChunkDelta, LLMUsage
from dify_graph.model_runtime.entities.message_entities import (
AssistantPromptMessage, AssistantPromptMessage,
PromptMessage, PromptMessage,
PromptMessageTool, PromptMessageTool,
ToolPromptMessage, ToolPromptMessage,
UserPromptMessage, UserPromptMessage,
) )
from dify_graph.nodes.agent.exc import AgentMaxIterationError from core.ops.ops_trace_manager import TraceQueueManager
from core.prompt.agent_history_prompt_transform import AgentHistoryPromptTransform
from core.tools.__base.tool import Tool
from core.tools.entities.tool_entities import ToolInvokeMeta
from core.tools.tool_engine import ToolEngine
from core.workflow.nodes.agent.exc import AgentMaxIterationError
from models.model import Message from models.model import Message
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
+4 -4
View File
@@ -1,16 +1,16 @@
import json import json
from core.agent.cot_agent_runner import CotAgentRunner from core.agent.cot_agent_runner import CotAgentRunner
from dify_graph.file import file_manager from core.model_runtime.entities import (
from dify_graph.model_runtime.entities import (
AssistantPromptMessage, AssistantPromptMessage,
PromptMessage, PromptMessage,
SystemPromptMessage, SystemPromptMessage,
TextPromptMessageContent, TextPromptMessageContent,
UserPromptMessage, UserPromptMessage,
) )
from dify_graph.model_runtime.entities.message_entities import ImagePromptMessageContent, PromptMessageContentUnionTypes from core.model_runtime.entities.message_entities import ImagePromptMessageContent, PromptMessageContentUnionTypes
from dify_graph.model_runtime.utils.encoders import jsonable_encoder from core.model_runtime.utils.encoders import jsonable_encoder
from core.workflow.file import file_manager
class CotChatAgentRunner(CotAgentRunner): class CotChatAgentRunner(CotAgentRunner):
@@ -1,13 +1,13 @@
import json import json
from core.agent.cot_agent_runner import CotAgentRunner from core.agent.cot_agent_runner import CotAgentRunner
from dify_graph.model_runtime.entities.message_entities import ( from core.model_runtime.entities.message_entities import (
AssistantPromptMessage, AssistantPromptMessage,
PromptMessage, PromptMessage,
TextPromptMessageContent, TextPromptMessageContent,
UserPromptMessage, UserPromptMessage,
) )
from dify_graph.model_runtime.utils.encoders import jsonable_encoder from core.model_runtime.utils.encoders import jsonable_encoder
class CotCompletionAgentRunner(CotAgentRunner): class CotCompletionAgentRunner(CotAgentRunner):
+7 -7
View File
@@ -7,11 +7,7 @@ from typing import Any, Union
from core.agent.base_agent_runner import BaseAgentRunner from core.agent.base_agent_runner import BaseAgentRunner
from core.app.apps.base_app_queue_manager import PublishFrom from core.app.apps.base_app_queue_manager import PublishFrom
from core.app.entities.queue_entities import QueueAgentThoughtEvent, QueueMessageEndEvent, QueueMessageFileEvent from core.app.entities.queue_entities import QueueAgentThoughtEvent, QueueMessageEndEvent, QueueMessageFileEvent
from core.prompt.agent_history_prompt_transform import AgentHistoryPromptTransform from core.model_runtime.entities import (
from core.tools.entities.tool_entities import ToolInvokeMeta
from core.tools.tool_engine import ToolEngine
from dify_graph.file import file_manager
from dify_graph.model_runtime.entities import (
AssistantPromptMessage, AssistantPromptMessage,
LLMResult, LLMResult,
LLMResultChunk, LLMResultChunk,
@@ -24,8 +20,12 @@ from dify_graph.model_runtime.entities import (
ToolPromptMessage, ToolPromptMessage,
UserPromptMessage, UserPromptMessage,
) )
from dify_graph.model_runtime.entities.message_entities import ImagePromptMessageContent, PromptMessageContentUnionTypes from core.model_runtime.entities.message_entities import ImagePromptMessageContent, PromptMessageContentUnionTypes
from dify_graph.nodes.agent.exc import AgentMaxIterationError from core.prompt.agent_history_prompt_transform import AgentHistoryPromptTransform
from core.tools.entities.tool_entities import ToolInvokeMeta
from core.tools.tool_engine import ToolEngine
from core.workflow.file import file_manager
from core.workflow.nodes.agent.exc import AgentMaxIterationError
from models.model import Message from models.model import Message
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -4,7 +4,7 @@ from collections.abc import Generator
from typing import Union from typing import Union
from core.agent.entities import AgentScratchpadUnit from core.agent.entities import AgentScratchpadUnit
from dify_graph.model_runtime.entities.llm_entities import LLMResultChunk from core.model_runtime.entities.llm_entities import LLMResultChunk
class CotAgentOutputParser: class CotAgentOutputParser:
@@ -1,13 +1,10 @@
from collections.abc import Mapping
from typing import Any
from core.app.app_config.entities import SensitiveWordAvoidanceEntity from core.app.app_config.entities import SensitiveWordAvoidanceEntity
from core.moderation.factory import ModerationFactory from core.moderation.factory import ModerationFactory
class SensitiveWordAvoidanceConfigManager: class SensitiveWordAvoidanceConfigManager:
@classmethod @classmethod
def convert(cls, config: Mapping[str, Any]) -> SensitiveWordAvoidanceEntity | None: def convert(cls, config: dict) -> SensitiveWordAvoidanceEntity | None:
sensitive_word_avoidance_dict = config.get("sensitive_word_avoidance") sensitive_word_avoidance_dict = config.get("sensitive_word_avoidance")
if not sensitive_word_avoidance_dict: if not sensitive_word_avoidance_dict:
return None return None
@@ -15,7 +12,7 @@ class SensitiveWordAvoidanceConfigManager:
if sensitive_word_avoidance_dict.get("enabled"): if sensitive_word_avoidance_dict.get("enabled"):
return SensitiveWordAvoidanceEntity( return SensitiveWordAvoidanceEntity(
type=sensitive_word_avoidance_dict.get("type"), type=sensitive_word_avoidance_dict.get("type"),
config=sensitive_word_avoidance_dict.get("config", {}), config=sensitive_word_avoidance_dict.get("config"),
) )
else: else:
return None return None
@@ -1,13 +1,10 @@
from typing import Any, cast
from core.agent.entities import AgentEntity, AgentPromptEntity, AgentToolEntity from core.agent.entities import AgentEntity, AgentPromptEntity, AgentToolEntity
from core.agent.prompt.template import REACT_PROMPT_TEMPLATES from core.agent.prompt.template import REACT_PROMPT_TEMPLATES
from models.model import AppModelConfigDict
class AgentConfigManager: class AgentConfigManager:
@classmethod @classmethod
def convert(cls, config: AppModelConfigDict) -> AgentEntity | None: def convert(cls, config: dict) -> AgentEntity | None:
""" """
Convert model config to model config Convert model config to model config
@@ -31,17 +28,17 @@ class AgentConfigManager:
agent_tools = [] agent_tools = []
for tool in agent_dict.get("tools", []): for tool in agent_dict.get("tools", []):
tool_dict = cast(dict[str, Any], tool) keys = tool.keys()
if len(tool_dict) >= 4: if len(keys) >= 4:
if "enabled" not in tool_dict or not tool_dict["enabled"]: if "enabled" not in tool or not tool["enabled"]:
continue continue
agent_tool_properties = { agent_tool_properties = {
"provider_type": tool_dict["provider_type"], "provider_type": tool["provider_type"],
"provider_id": tool_dict["provider_id"], "provider_id": tool["provider_id"],
"tool_name": tool_dict["tool_name"], "tool_name": tool["tool_name"],
"tool_parameters": tool_dict.get("tool_parameters", {}), "tool_parameters": tool.get("tool_parameters", {}),
"credential_id": tool_dict.get("credential_id", None), "credential_id": tool.get("credential_id", None),
} }
agent_tools.append(AgentToolEntity.model_validate(agent_tool_properties)) agent_tools.append(AgentToolEntity.model_validate(agent_tool_properties))
@@ -50,8 +47,7 @@ class AgentConfigManager:
"react_router", "react_router",
"router", "router",
}: }:
agent_prompt_raw = agent_dict.get("prompt", None) agent_prompt = agent_dict.get("prompt", None) or {}
agent_prompt: dict[str, Any] = agent_prompt_raw if isinstance(agent_prompt_raw, dict) else {}
# check model mode # check model mode
model_mode = config.get("model", {}).get("mode", "completion") model_mode = config.get("model", {}).get("mode", "completion")
if model_mode == "completion": if model_mode == "completion":
@@ -79,7 +75,7 @@ class AgentConfigManager:
strategy=strategy, strategy=strategy,
prompt=agent_prompt_entity, prompt=agent_prompt_entity,
tools=agent_tools, tools=agent_tools,
max_iteration=cast(int, agent_dict.get("max_iteration", 10)), max_iteration=agent_dict.get("max_iteration", 10),
) )
return None return None
@@ -1,5 +1,5 @@
import uuid import uuid
from typing import Any, Literal, cast from typing import Literal, cast
from core.app.app_config.entities import ( from core.app.app_config.entities import (
DatasetEntity, DatasetEntity,
@@ -8,13 +8,13 @@ from core.app.app_config.entities import (
ModelConfig, ModelConfig,
) )
from core.entities.agent_entities import PlanningStrategy from core.entities.agent_entities import PlanningStrategy
from models.model import AppMode, AppModelConfigDict from models.model import AppMode
from services.dataset_service import DatasetService from services.dataset_service import DatasetService
class DatasetConfigManager: class DatasetConfigManager:
@classmethod @classmethod
def convert(cls, config: AppModelConfigDict) -> DatasetEntity | None: def convert(cls, config: dict) -> DatasetEntity | None:
""" """
Convert model config to model config Convert model config to model config
@@ -25,15 +25,11 @@ class DatasetConfigManager:
datasets = config.get("dataset_configs", {}).get("datasets", {"strategy": "router", "datasets": []}) datasets = config.get("dataset_configs", {}).get("datasets", {"strategy": "router", "datasets": []})
for dataset in datasets.get("datasets", []): for dataset in datasets.get("datasets", []):
if not isinstance(dataset, dict):
continue
keys = list(dataset.keys()) keys = list(dataset.keys())
if len(keys) == 0 or keys[0] != "dataset": if len(keys) == 0 or keys[0] != "dataset":
continue continue
dataset = dataset["dataset"] dataset = dataset["dataset"]
if not isinstance(dataset, dict):
continue
if "enabled" not in dataset or not dataset["enabled"]: if "enabled" not in dataset or not dataset["enabled"]:
continue continue
@@ -51,14 +47,15 @@ class DatasetConfigManager:
agent_dict = config.get("agent_mode", {}) agent_dict = config.get("agent_mode", {})
for tool in agent_dict.get("tools", []): for tool in agent_dict.get("tools", []):
if len(tool) == 1: keys = tool.keys()
if len(keys) == 1:
# old standard # old standard
key = list(tool.keys())[0] key = list(tool.keys())[0]
if key != "dataset": if key != "dataset":
continue continue
tool_item = cast(dict[str, Any], tool)[key] tool_item = tool[key]
if "enabled" not in tool_item or not tool_item["enabled"]: if "enabled" not in tool_item or not tool_item["enabled"]:
continue continue
@@ -4,10 +4,10 @@ from core.app.app_config.entities import EasyUIBasedAppConfig
from core.app.entities.app_invoke_entities import ModelConfigWithCredentialsEntity from core.app.entities.app_invoke_entities import ModelConfigWithCredentialsEntity
from core.entities.model_entities import ModelStatus from core.entities.model_entities import ModelStatus
from core.errors.error import ModelCurrentlyNotSupportError, ProviderTokenNotInitError, QuotaExceededError from core.errors.error import ModelCurrentlyNotSupportError, ProviderTokenNotInitError, QuotaExceededError
from core.model_runtime.entities.llm_entities import LLMMode
from core.model_runtime.entities.model_entities import ModelPropertyKey, ModelType
from core.model_runtime.model_providers.__base.large_language_model import LargeLanguageModel
from core.provider_manager import ProviderManager from core.provider_manager import ProviderManager
from dify_graph.model_runtime.entities.llm_entities import LLMMode
from dify_graph.model_runtime.entities.model_entities import ModelPropertyKey, ModelType
from dify_graph.model_runtime.model_providers.__base.large_language_model import LargeLanguageModel
class ModelConfigConverter: class ModelConfigConverter:
@@ -2,16 +2,15 @@ from collections.abc import Mapping
from typing import Any from typing import Any
from core.app.app_config.entities import ModelConfigEntity from core.app.app_config.entities import ModelConfigEntity
from core.model_runtime.entities.model_entities import ModelPropertyKey, ModelType
from core.model_runtime.model_providers.model_provider_factory import ModelProviderFactory
from core.provider_manager import ProviderManager from core.provider_manager import ProviderManager
from dify_graph.model_runtime.entities.model_entities import ModelPropertyKey, ModelType
from dify_graph.model_runtime.model_providers.model_provider_factory import ModelProviderFactory
from models.model import AppModelConfigDict
from models.provider_ids import ModelProviderID from models.provider_ids import ModelProviderID
class ModelConfigManager: class ModelConfigManager:
@classmethod @classmethod
def convert(cls, config: AppModelConfigDict) -> ModelConfigEntity: def convert(cls, config: dict) -> ModelConfigEntity:
""" """
Convert model config to model config Convert model config to model config
@@ -23,7 +22,7 @@ class ModelConfigManager:
if not model_config: if not model_config:
raise ValueError("model is required") raise ValueError("model is required")
completion_params = model_config.get("completion_params") or {} completion_params = model_config.get("completion_params")
stop = [] stop = []
if "stop" in completion_params: if "stop" in completion_params:
stop = completion_params["stop"] stop = completion_params["stop"]
@@ -1,19 +1,17 @@
from typing import Any
from core.app.app_config.entities import ( from core.app.app_config.entities import (
AdvancedChatMessageEntity, AdvancedChatMessageEntity,
AdvancedChatPromptTemplateEntity, AdvancedChatPromptTemplateEntity,
AdvancedCompletionPromptTemplateEntity, AdvancedCompletionPromptTemplateEntity,
PromptTemplateEntity, PromptTemplateEntity,
) )
from core.model_runtime.entities.message_entities import PromptMessageRole
from core.prompt.simple_prompt_transform import ModelMode from core.prompt.simple_prompt_transform import ModelMode
from dify_graph.model_runtime.entities.message_entities import PromptMessageRole from models.model import AppMode
from models.model import AppMode, AppModelConfigDict
class PromptTemplateConfigManager: class PromptTemplateConfigManager:
@classmethod @classmethod
def convert(cls, config: AppModelConfigDict) -> PromptTemplateEntity: def convert(cls, config: dict) -> PromptTemplateEntity:
if not config.get("prompt_type"): if not config.get("prompt_type"):
raise ValueError("prompt_type is required") raise ValueError("prompt_type is required")
@@ -42,15 +40,14 @@ class PromptTemplateConfigManager:
advanced_completion_prompt_template = None advanced_completion_prompt_template = None
completion_prompt_config = config.get("completion_prompt_config", {}) completion_prompt_config = config.get("completion_prompt_config", {})
if completion_prompt_config: if completion_prompt_config:
completion_prompt_template_params: dict[str, Any] = { completion_prompt_template_params = {
"prompt": completion_prompt_config["prompt"]["text"], "prompt": completion_prompt_config["prompt"]["text"],
} }
conv_role = completion_prompt_config.get("conversation_histories_role") if "conversation_histories_role" in completion_prompt_config:
if conv_role:
completion_prompt_template_params["role_prefix"] = { completion_prompt_template_params["role_prefix"] = {
"user": conv_role["user_prefix"], "user": completion_prompt_config["conversation_histories_role"]["user_prefix"],
"assistant": conv_role["assistant_prefix"], "assistant": completion_prompt_config["conversation_histories_role"]["assistant_prefix"],
} }
advanced_completion_prompt_template = AdvancedCompletionPromptTemplateEntity( advanced_completion_prompt_template = AdvancedCompletionPromptTemplateEntity(
@@ -1,10 +1,7 @@
import re import re
from typing import cast
from core.app.app_config.entities import ExternalDataVariableEntity from core.app.app_config.entities import ExternalDataVariableEntity, VariableEntity, VariableEntityType
from core.external_data_tool.factory import ExternalDataToolFactory from core.external_data_tool.factory import ExternalDataToolFactory
from dify_graph.variables.input_entities import VariableEntity, VariableEntityType
from models.model import AppModelConfigDict
_ALLOWED_VARIABLE_ENTITY_TYPE = frozenset( _ALLOWED_VARIABLE_ENTITY_TYPE = frozenset(
[ [
@@ -20,7 +17,7 @@ _ALLOWED_VARIABLE_ENTITY_TYPE = frozenset(
class BasicVariablesConfigManager: class BasicVariablesConfigManager:
@classmethod @classmethod
def convert(cls, config: AppModelConfigDict) -> tuple[list[VariableEntity], list[ExternalDataVariableEntity]]: def convert(cls, config: dict) -> tuple[list[VariableEntity], list[ExternalDataVariableEntity]]:
""" """
Convert model config to model config Convert model config to model config
@@ -53,9 +50,7 @@ class BasicVariablesConfigManager:
external_data_variables.append( external_data_variables.append(
ExternalDataVariableEntity( ExternalDataVariableEntity(
variable=variable["variable"], variable=variable["variable"], type=variable["type"], config=variable["config"]
type=variable.get("type", ""),
config=variable.get("config", {}),
) )
) )
elif variable_type in { elif variable_type in {
@@ -68,10 +63,10 @@ class BasicVariablesConfigManager:
variable = variables[variable_type] variable = variables[variable_type]
variable_entities.append( variable_entities.append(
VariableEntity( VariableEntity(
type=cast(VariableEntityType, variable_type), type=variable_type,
variable=variable["variable"], variable=variable.get("variable"),
description=variable.get("description") or "", description=variable.get("description") or "",
label=variable["label"], label=variable.get("label"),
required=variable.get("required", False), required=variable.get("required", False),
max_length=variable.get("max_length"), max_length=variable.get("max_length"),
options=variable.get("options") or [], options=variable.get("options") or [],
+62 -8
View File
@@ -2,12 +2,12 @@ from collections.abc import Sequence
from enum import StrEnum, auto from enum import StrEnum, auto
from typing import Any, Literal from typing import Any, Literal
from pydantic import BaseModel, Field from jsonschema import Draft7Validator, SchemaError
from pydantic import BaseModel, Field, field_validator
from dify_graph.file import FileUploadConfig from core.model_runtime.entities.llm_entities import LLMMode
from dify_graph.model_runtime.entities.llm_entities import LLMMode from core.model_runtime.entities.message_entities import PromptMessageRole
from dify_graph.model_runtime.entities.message_entities import PromptMessageRole from core.workflow.file import FileTransferMethod, FileType, FileUploadConfig
from dify_graph.variables.input_entities import VariableEntity as WorkflowVariableEntity
from models.model import AppMode from models.model import AppMode
@@ -90,7 +90,61 @@ class PromptTemplateEntity(BaseModel):
advanced_completion_prompt_template: AdvancedCompletionPromptTemplateEntity | None = None advanced_completion_prompt_template: AdvancedCompletionPromptTemplateEntity | None = None
class RagPipelineVariableEntity(WorkflowVariableEntity): class VariableEntityType(StrEnum):
TEXT_INPUT = "text-input"
SELECT = "select"
PARAGRAPH = "paragraph"
NUMBER = "number"
EXTERNAL_DATA_TOOL = "external_data_tool"
FILE = "file"
FILE_LIST = "file-list"
CHECKBOX = "checkbox"
JSON_OBJECT = "json_object"
class VariableEntity(BaseModel):
"""
Variable Entity.
"""
# `variable` records the name of the variable in user inputs.
variable: str
label: str
description: str = ""
type: VariableEntityType
required: bool = False
hide: bool = False
default: Any = None
max_length: int | None = None
options: Sequence[str] = Field(default_factory=list)
allowed_file_types: Sequence[FileType] | None = Field(default_factory=list)
allowed_file_extensions: Sequence[str] | None = Field(default_factory=list)
allowed_file_upload_methods: Sequence[FileTransferMethod] | None = Field(default_factory=list)
json_schema: dict | None = Field(default=None)
@field_validator("description", mode="before")
@classmethod
def convert_none_description(cls, v: Any) -> str:
return v or ""
@field_validator("options", mode="before")
@classmethod
def convert_none_options(cls, v: Any) -> Sequence[str]:
return v or []
@field_validator("json_schema")
@classmethod
def validate_json_schema(cls, schema: dict | None) -> dict | None:
if schema is None:
return None
try:
Draft7Validator.check_schema(schema)
except SchemaError as e:
raise ValueError(f"Invalid JSON schema: {e.message}")
return schema
class RagPipelineVariableEntity(VariableEntity):
""" """
Rag Pipeline Variable Entity. Rag Pipeline Variable Entity.
""" """
@@ -260,7 +314,7 @@ class AppConfig(BaseModel):
app_id: str app_id: str
app_mode: AppMode app_mode: AppMode
additional_features: AppAdditionalFeatures | None = None additional_features: AppAdditionalFeatures | None = None
variables: list[WorkflowVariableEntity] = [] variables: list[VariableEntity] = []
sensitive_word_avoidance: SensitiveWordAvoidanceEntity | None = None sensitive_word_avoidance: SensitiveWordAvoidanceEntity | None = None
@@ -281,7 +335,7 @@ class EasyUIBasedAppConfig(AppConfig):
app_model_config_from: EasyUIBasedAppModelConfigFrom app_model_config_from: EasyUIBasedAppModelConfigFrom
app_model_config_id: str app_model_config_id: str
app_model_config_dict: dict[str, Any] app_model_config_dict: dict
model: ModelConfigEntity model: ModelConfigEntity
prompt_template: PromptTemplateEntity prompt_template: PromptTemplateEntity
dataset: DatasetEntity | None = None dataset: DatasetEntity | None = None
@@ -2,7 +2,7 @@ from collections.abc import Mapping
from typing import Any from typing import Any
from constants import DEFAULT_FILE_NUMBER_LIMITS from constants import DEFAULT_FILE_NUMBER_LIMITS
from dify_graph.file import FileUploadConfig from core.workflow.file import FileUploadConfig
class FileUploadConfigManager: class FileUploadConfigManager:
@@ -1,7 +1,6 @@
import re import re
from core.app.app_config.entities import RagPipelineVariableEntity from core.app.app_config.entities import RagPipelineVariableEntity, VariableEntity
from dify_graph.variables.input_entities import VariableEntity
from models.workflow import Workflow from models.workflow import Workflow
@@ -31,18 +31,18 @@ from core.app.entities.app_invoke_entities import AdvancedChatAppGenerateEntity,
from core.app.entities.task_entities import ChatbotAppBlockingResponse, ChatbotAppStreamResponse from core.app.entities.task_entities import ChatbotAppBlockingResponse, ChatbotAppStreamResponse
from core.app.layers.pause_state_persist_layer import PauseStateLayerConfig, PauseStatePersistenceLayer from core.app.layers.pause_state_persist_layer import PauseStateLayerConfig, PauseStatePersistenceLayer
from core.helper.trace_id_helper import extract_external_trace_id_from_args from core.helper.trace_id_helper import extract_external_trace_id_from_args
from core.model_runtime.errors.invoke import InvokeAuthorizationError
from core.ops.ops_trace_manager import TraceQueueManager from core.ops.ops_trace_manager import TraceQueueManager
from core.prompt.utils.get_thread_messages_length import get_thread_messages_length from core.prompt.utils.get_thread_messages_length import get_thread_messages_length
from core.repositories import DifyCoreRepositoryFactory from core.repositories import DifyCoreRepositoryFactory
from dify_graph.graph_engine.layers.base import GraphEngineLayer from core.workflow.graph_engine.layers.base import GraphEngineLayer
from dify_graph.model_runtime.errors.invoke import InvokeAuthorizationError from core.workflow.repositories.draft_variable_repository import (
from dify_graph.repositories.draft_variable_repository import (
DraftVariableSaverFactory, DraftVariableSaverFactory,
) )
from dify_graph.repositories.workflow_execution_repository import WorkflowExecutionRepository from core.workflow.repositories.workflow_execution_repository import WorkflowExecutionRepository
from dify_graph.repositories.workflow_node_execution_repository import WorkflowNodeExecutionRepository from core.workflow.repositories.workflow_node_execution_repository import WorkflowNodeExecutionRepository
from dify_graph.runtime import GraphRuntimeState from core.workflow.runtime import GraphRuntimeState
from dify_graph.variable_loader import DUMMY_VARIABLE_LOADER, VariableLoader from core.workflow.variable_loader import DUMMY_VARIABLE_LOADER, VariableLoader
from extensions.ext_database import db from extensions.ext_database import db
from factories import file_factory from factories import file_factory
from libs.flask_utils import preserve_flask_contexts from libs.flask_utils import preserve_flask_contexts
@@ -25,16 +25,16 @@ from core.app.workflow.layers.persistence import PersistenceWorkflowInfo, Workfl
from core.db.session_factory import session_factory from core.db.session_factory import session_factory
from core.moderation.base import ModerationError from core.moderation.base import ModerationError
from core.moderation.input_moderation import InputModeration from core.moderation.input_moderation import InputModeration
from core.variables.variables import Variable
from core.workflow.enums import WorkflowType
from core.workflow.graph_engine.command_channels.redis_channel import RedisChannel
from core.workflow.graph_engine.layers.base import GraphEngineLayer
from core.workflow.repositories.workflow_execution_repository import WorkflowExecutionRepository
from core.workflow.repositories.workflow_node_execution_repository import WorkflowNodeExecutionRepository
from core.workflow.runtime import GraphRuntimeState, VariablePool
from core.workflow.system_variable import SystemVariable
from core.workflow.variable_loader import VariableLoader
from core.workflow.workflow_entry import WorkflowEntry from core.workflow.workflow_entry import WorkflowEntry
from dify_graph.enums import WorkflowType
from dify_graph.graph_engine.command_channels.redis_channel import RedisChannel
from dify_graph.graph_engine.layers.base import GraphEngineLayer
from dify_graph.repositories.workflow_execution_repository import WorkflowExecutionRepository
from dify_graph.repositories.workflow_node_execution_repository import WorkflowNodeExecutionRepository
from dify_graph.runtime import GraphRuntimeState, VariablePool
from dify_graph.system_variable import SystemVariable
from dify_graph.variable_loader import VariableLoader
from dify_graph.variables.variables import Variable
from extensions.ext_database import db from extensions.ext_database import db
from extensions.ext_redis import redis_client from extensions.ext_redis import redis_client
from extensions.otel import WorkflowAppRunnerHandler, trace_span from extensions.otel import WorkflowAppRunnerHandler, trace_span
@@ -63,16 +63,16 @@ from core.app.entities.task_entities import (
from core.app.task_pipeline.based_generate_task_pipeline import BasedGenerateTaskPipeline from core.app.task_pipeline.based_generate_task_pipeline import BasedGenerateTaskPipeline
from core.app.task_pipeline.message_cycle_manager import MessageCycleManager from core.app.task_pipeline.message_cycle_manager import MessageCycleManager
from core.base.tts import AppGeneratorTTSPublisher, AudioTrunk from core.base.tts import AppGeneratorTTSPublisher, AudioTrunk
from core.model_runtime.entities.llm_entities import LLMUsage
from core.model_runtime.utils.encoders import jsonable_encoder
from core.ops.ops_trace_manager import TraceQueueManager from core.ops.ops_trace_manager import TraceQueueManager
from core.repositories.human_input_repository import HumanInputFormRepositoryImpl from core.repositories.human_input_repository import HumanInputFormRepositoryImpl
from dify_graph.entities.pause_reason import HumanInputRequired from core.workflow.entities.pause_reason import HumanInputRequired
from dify_graph.enums import WorkflowExecutionStatus from core.workflow.enums import WorkflowExecutionStatus
from dify_graph.model_runtime.entities.llm_entities import LLMUsage from core.workflow.nodes import NodeType
from dify_graph.model_runtime.utils.encoders import jsonable_encoder from core.workflow.repositories.draft_variable_repository import DraftVariableSaverFactory
from dify_graph.nodes import NodeType from core.workflow.runtime import GraphRuntimeState
from dify_graph.repositories.draft_variable_repository import DraftVariableSaverFactory from core.workflow.system_variable import SystemVariable
from dify_graph.runtime import GraphRuntimeState
from dify_graph.system_variable import SystemVariable
from extensions.ext_database import db from extensions.ext_database import db
from libs.datetime_utils import naive_utc_now from libs.datetime_utils import naive_utc_now
from models import Account, Conversation, EndUser, Message, MessageFile from models import Account, Conversation, EndUser, Message, MessageFile
@@ -516,10 +516,8 @@ class AdvancedChatAppGenerateTaskPipeline(GraphRuntimeStateSupport):
graph_runtime_state=validated_state, graph_runtime_state=validated_state,
) )
yield from self._handle_advanced_chat_message_end_event(
QueueAdvancedChatMessageEndEvent(), graph_runtime_state=validated_state
)
yield workflow_finish_resp yield workflow_finish_resp
self._base_task_pipeline.queue_manager.publish(QueueAdvancedChatMessageEndEvent(), PublishFrom.TASK_PIPELINE)
def _handle_workflow_partial_success_event( def _handle_workflow_partial_success_event(
self, self,
@@ -540,9 +538,6 @@ class AdvancedChatAppGenerateTaskPipeline(GraphRuntimeStateSupport):
exceptions_count=event.exceptions_count, exceptions_count=event.exceptions_count,
) )
yield from self._handle_advanced_chat_message_end_event(
QueueAdvancedChatMessageEndEvent(), graph_runtime_state=validated_state
)
yield workflow_finish_resp yield workflow_finish_resp
def _handle_workflow_paused_event( def _handle_workflow_paused_event(
@@ -740,6 +735,7 @@ class AdvancedChatAppGenerateTaskPipeline(GraphRuntimeStateSupport):
def _load_human_input_form_id(self, *, node_id: str) -> str | None: def _load_human_input_form_id(self, *, node_id: str) -> str | None:
form_repository = HumanInputFormRepositoryImpl( form_repository = HumanInputFormRepositoryImpl(
session_factory=db.engine,
tenant_id=self._workflow_tenant_id, tenant_id=self._workflow_tenant_id,
) )
form = form_repository.get_form(self._workflow_run_id, node_id) form = form_repository.get_form(self._workflow_run_id, node_id)
@@ -859,14 +855,6 @@ class AdvancedChatAppGenerateTaskPipeline(GraphRuntimeStateSupport):
yield from self._handle_workflow_paused_event(event) yield from self._handle_workflow_paused_event(event)
break break
case QueueWorkflowSucceededEvent():
yield from self._handle_workflow_succeeded_event(event, trace_manager=trace_manager)
break
case QueueWorkflowPartialSuccessEvent():
yield from self._handle_workflow_partial_success_event(event, trace_manager=trace_manager)
break
case QueueStopEvent(): case QueueStopEvent():
yield from self._handle_stop_event(event, graph_runtime_state=None, trace_manager=trace_manager) yield from self._handle_stop_event(event, graph_runtime_state=None, trace_manager=trace_manager)
break break
@@ -20,7 +20,7 @@ from core.app.app_config.features.suggested_questions_after_answer.manager impor
) )
from core.app.app_config.features.text_to_speech.manager import TextToSpeechConfigManager from core.app.app_config.features.text_to_speech.manager import TextToSpeechConfigManager
from core.entities.agent_entities import PlanningStrategy from core.entities.agent_entities import PlanningStrategy
from models.model import App, AppMode, AppModelConfig, AppModelConfigDict, Conversation from models.model import App, AppMode, AppModelConfig, Conversation
OLD_TOOLS = ["dataset", "google_search", "web_reader", "wikipedia", "current_datetime"] OLD_TOOLS = ["dataset", "google_search", "web_reader", "wikipedia", "current_datetime"]
@@ -40,7 +40,7 @@ class AgentChatAppConfigManager(BaseAppConfigManager):
app_model: App, app_model: App,
app_model_config: AppModelConfig, app_model_config: AppModelConfig,
conversation: Conversation | None = None, conversation: Conversation | None = None,
override_config_dict: AppModelConfigDict | None = None, override_config_dict: dict | None = None,
) -> AgentChatAppConfig: ) -> AgentChatAppConfig:
""" """
Convert app model config to agent chat app config Convert app model config to agent chat app config
@@ -61,9 +61,7 @@ class AgentChatAppConfigManager(BaseAppConfigManager):
app_model_config_dict = app_model_config.to_dict() app_model_config_dict = app_model_config.to_dict()
config_dict = app_model_config_dict.copy() config_dict = app_model_config_dict.copy()
else: else:
if not override_config_dict: config_dict = override_config_dict or {}
raise Exception("override_config_dict is required when config_from is ARGS")
config_dict = override_config_dict
app_mode = AppMode.value_of(app_model.mode) app_mode = AppMode.value_of(app_model.mode)
app_config = AgentChatAppConfig( app_config = AgentChatAppConfig(
@@ -72,7 +70,7 @@ class AgentChatAppConfigManager(BaseAppConfigManager):
app_mode=app_mode, app_mode=app_mode,
app_model_config_from=config_from, app_model_config_from=config_from,
app_model_config_id=app_model_config.id, app_model_config_id=app_model_config.id,
app_model_config_dict=cast(dict[str, Any], config_dict), app_model_config_dict=config_dict,
model=ModelConfigManager.convert(config=config_dict), model=ModelConfigManager.convert(config=config_dict),
prompt_template=PromptTemplateConfigManager.convert(config=config_dict), prompt_template=PromptTemplateConfigManager.convert(config=config_dict),
sensitive_word_avoidance=SensitiveWordAvoidanceConfigManager.convert(config=config_dict), sensitive_word_avoidance=SensitiveWordAvoidanceConfigManager.convert(config=config_dict),
@@ -88,7 +86,7 @@ class AgentChatAppConfigManager(BaseAppConfigManager):
return app_config return app_config
@classmethod @classmethod
def config_validate(cls, tenant_id: str, config: Mapping[str, Any]) -> AppModelConfigDict: def config_validate(cls, tenant_id: str, config: Mapping[str, Any]):
""" """
Validate for agent chat app model config Validate for agent chat app model config
@@ -159,7 +157,7 @@ class AgentChatAppConfigManager(BaseAppConfigManager):
# Filter out extra parameters # Filter out extra parameters
filtered_config = {key: config.get(key) for key in related_config_keys} filtered_config = {key: config.get(key) for key in related_config_keys}
return cast(AppModelConfigDict, filtered_config) return filtered_config
@classmethod @classmethod
def validate_agent_mode_and_set_defaults( def validate_agent_mode_and_set_defaults(

Some files were not shown because too many files have changed in this diff Show More