Files
dify/.github/workflows/knowledge-fs-ci.yml
T
-LAN-andautofix-ci[bot] 53ea596fc7 fix(docker): honor categorized service environment overrides (#45359)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-11-03 01:58:43 +00:00

534 lines
23 KiB
YAML

name: KnowledgeFS CI
on:
pull_request:
branches: ['main']
merge_group:
branches: ['main']
types: [checks_requested]
push:
branches: ['main', 'deploy/konwledge']
workflow_dispatch:
permissions:
contents: read
pull-requests: read
concurrency:
group: knowledge-fs-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
env:
CI: true
jobs:
check-changes:
name: Check KnowledgeFS changes
runs-on: depot-ubuntu-24.04-4
outputs:
knowledge-fs: ${{ steps.changes.outputs.knowledge-fs }}
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
persist-credentials: false
- name: Detect KnowledgeFS changes
id: changes
uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3
with:
filters: |
knowledge-fs:
- 'api/knowledge-fs-contract/**'
- 'api/tests/e2e_tests/knowledge_fs_vector/**'
- 'api/services/tidb_binding_service.py'
- 'api/tests/unit_tests/services/test_tidb_binding_service.py'
- 'api/providers/vdb/vdb-tidb-on-qdrant/**'
- 'api/Dockerfile'
- 'api/dev/generate_knowledge_fs_contract.py'
- 'api/dev/knowledge_fs_product_contract.py'
- 'api/knowledge-fs-contract.lock.json'
- 'api/knowledge-fs-product-operation-gaps.json'
- 'api/knowledge-fs-product-operations.json'
- 'api/**/knowledge_fs/**'
- 'api/**/*knowledge_fs*'
- 'api/**/*knowledge-fs*'
- 'api/.env.example'
- 'api/app_factory.py'
- 'api/commands/__init__.py'
- 'api/controllers/common/errors.py'
- 'api/controllers/console/__init__.py'
- 'api/controllers/console/workspace/rbac/**'
- 'api/controllers/inner_api/__init__.py'
- 'api/controllers/service_api/__init__.py'
- 'api/core/agent/base_agent_runner.py'
- 'api/core/app/apps/agent_app/runtime_request_builder.py'
- 'api/core/rbac/entities.py'
- 'api/core/tools/__base/tool_runtime.py'
- 'api/core/tools/builtin_tool/_position.yaml'
- 'api/core/workflow/node_runtime.py'
- 'api/core/workflow/nodes/agent_v2/runtime_request_builder.py'
- 'api/extensions/ext_celery.py'
- 'api/extensions/ext_commands.py'
- 'api/models/__init__.py'
- 'api/models/enums.py'
- 'api/models/model.py'
- 'api/repositories/workspace/workspace_repository.py'
- 'api/services/workspace/member_service.py'
- 'api/services/agent_tool_inner_service.py'
- 'api/services/enterprise/rbac_service.py'
- 'api/services/entities/agent_tool_inner.py'
- 'api/services/vector_space_admission_service.py'
- 'api/services/knowledge_fs/**'
- 'api/services/knowledge_fs_capability.py'
- 'api/tests/unit_tests/dev/test_generate_knowledge_fs_contract.py'
- 'api/tests/unit_tests/extensions/test_ext_celery.py'
- 'api/tests/unit_tests/models/test_agent_config_entities.py'
- 'api/tests/unit_tests/controllers/common/test_errors.py'
- 'api/tests/unit_tests/controllers/console/datasets/test_data_source.py'
- 'api/tests/unit_tests/controllers/console/workspace/rbac/test_roles.py'
- 'api/tests/unit_tests/controllers/inner_api/agent/test_tools.py'
- 'api/tests/unit_tests/core/agent/test_base_agent_runner.py'
- 'api/tests/unit_tests/core/app/apps/agent_app/test_runtime_request_builder.py'
- 'api/tests/unit_tests/core/workflow/nodes/agent_v2/test_runtime_request_builder.py'
- 'api/tests/unit_tests/core/workflow/test_node_runtime.py'
- 'api/tests/unit_tests/services/enterprise/test_rbac_service.py'
- 'api/tests/unit_tests/repositories/workspace/test_workspace_repository.py'
- 'api/tests/unit_tests/services/test_agent_tool_inner_service.py'
- 'api/tests/unit_tests/services/test_vector_space_admission_service.py'
- 'api/tests/unit_tests/services/test_knowledge_fs_capability.py'
- 'api/tests/unit_tests/services/knowledge_fs/test_product_operations.py'
- 'api/pyproject.toml'
- 'api/uv.lock'
- 'dify-agent/src/dify_agent/layers/dify_core_tools/client.py'
- 'dify-agent/tests/local/dify_agent/layers/dify_core_tools/test_client.py'
- 'packages/contracts/generated/api/console/**'
- 'packages/contracts/generated/api/service/**'
- 'docker/.env.example'
- 'docker/README.md'
- 'docker/dify-env-sync.py'
- 'docker/dify-env-sync.sh'
- 'docker/docker-compose-template.yaml'
- 'docker/docker-compose.yaml'
- 'docker/envs/core-services/api.defaults'
- 'docker/envs/core-services/knowledge-fs.env.example'
- 'docker/envs/core-services/knowledge-fs-unstructured-service.defaults'
- 'docker/envs/core-services/knowledge-fs-unstructured.env.example'
- 'docker/knowledge-fs-unstructured-sandbox.compose.yaml'
- 'docker/generate_docker_compose'
- '.github/dependabot.yml'
- '.github/workflows/deploy-knowledge.yml'
- '.github/workflows/deploy-dev.yml'
- '.github/scripts/knowledge-deploy-*'
- '.github/workflows/knowledge-fs-ci.yml'
quality:
name: Run KnowledgeFS quality and contract gates
needs: check-changes
if: needs.check-changes.outputs.knowledge-fs == 'true' || github.event_name == 'workflow_dispatch'
runs-on: depot-ubuntu-24.04-4
defaults:
run:
shell: bash
working-directory: .
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
persist-credentials: false
- name: Setup Node for deployment gate regression tests
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: '22'
- name: Test deployment revision gates
run: node --test .github/scripts/knowledge-deploy-gates.test.cjs
- name: Setup UV and Python
uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3
with:
enable-cache: true
python-version: '3.13'
cache-dependency-glob: |
api/uv.lock
dify-agent/uv.lock
- name: Verify Dify dependency lock
working-directory: .
run: uv lock --project api --check
- name: Install Dify contract dependencies
working-directory: .
run: uv sync --project api --locked --dev --group vdb-qdrant --group vdb-elasticsearch --group vdb-weaviate --group vdb-tidb-on-qdrant
# The standalone KnowledgeFS repository owns TypeScript, parser, image and deployment gates.
# This repository verifies its immutable published contract and all Dify integration code.
- name: Verify pinned KnowledgeFS contract
run: uv run --project api --group vdb-qdrant --group vdb-elasticsearch --group vdb-weaviate --group vdb-tidb-on-qdrant python api/dev/generate_knowledge_fs_contract.py --check
- name: Collect Dify KnowledgeFS gate targets
working-directory: .
run: |
set -euo pipefail
target_dir="${RUNNER_TEMP:?}/knowledge-fs-ci-targets"
mkdir -p "$target_dir"
production_targets=()
add_production_target() {
local path="$1"
if [[ ! -f "$path" ]]; then
echo "required Dify KnowledgeFS production target is missing: $path" >&2
exit 1
fi
production_targets+=("$path")
}
while IFS= read -r -d '' path; do
if [[ "$path" == *knowledge_fs* ]]; then
add_production_target "$path"
fi
done < <(
find api \
\( -path 'api/.venv' -o -path 'api/tests' -o -path 'api/storage' \) -prune \
-o -type f -name '*.py' -print0
)
production_touchpoints=(
api/app_factory.py
api/commands/__init__.py
api/controllers/common/errors.py
api/controllers/console/__init__.py
api/controllers/console/datasets/datasets.py
api/controllers/console/workspace/rbac/__init__.py
api/controllers/console/workspace/rbac/members.py
api/controllers/console/workspace/rbac/policies.py
api/controllers/console/workspace/rbac/resources.py
api/controllers/console/workspace/rbac/roles.py
api/controllers/console/workspace/rbac/schemas.py
api/controllers/inner_api/__init__.py
api/controllers/service_api/__init__.py
api/core/agent/base_agent_runner.py
api/core/app/apps/agent_app/runtime_request_builder.py
api/core/rbac/entities.py
api/core/tools/__base/tool_runtime.py
api/core/workflow/node_runtime.py
api/core/workflow/nodes/agent_v2/runtime_request_builder.py
api/extensions/ext_celery.py
api/extensions/ext_commands.py
api/models/__init__.py
api/models/enums.py
api/models/model.py
api/repositories/workspace/workspace_repository.py
api/services/workspace/member_service.py
api/services/agent_tool_inner_service.py
api/services/enterprise/rbac_service.py
api/services/entities/agent_tool_inner.py
api/services/vector_space_admission_service.py
api/services/tidb_binding_service.py
api/providers/vdb/vdb-tidb-on-qdrant/src/dify_vdb_tidb_on_qdrant/tidb_service.py
api/providers/vdb/vdb-tidb-on-qdrant/src/dify_vdb_tidb_on_qdrant/tidb_on_qdrant_vector.py
)
for path in "${production_touchpoints[@]}"; do
add_production_target "$path"
done
printf '%s\0' "${production_touchpoints[@]}" > "$target_dir/glue-files"
if ((${#production_targets[@]} == 0)); then
echo "Dify KnowledgeFS production target set is empty" >&2
exit 1
fi
printf '%s\0' "${production_targets[@]}" > "$target_dir/production-files"
test_targets=()
add_test_target() {
local path="$1"
if [[ ! -f "$path" ]]; then
echo "required Dify KnowledgeFS unit test is missing: $path" >&2
exit 1
fi
test_targets+=("$path")
}
while IFS= read -r -d '' path; do
if [[ "$path" == *knowledge_fs* ]]; then
add_test_target "$path"
fi
done < <(find api/tests/unit_tests api/tests/e2e_tests/contracts api/tests/helpers/unit -type f -name '*.py' -print0)
test_touchpoints=(
api/tests/unit_tests/extensions/test_ext_celery.py
api/tests/unit_tests/models/test_agent_config_entities.py
api/tests/unit_tests/controllers/common/test_errors.py
api/tests/unit_tests/controllers/console/datasets/test_data_source.py
api/tests/unit_tests/controllers/console/datasets/test_datasets.py
api/tests/unit_tests/controllers/console/test_wraps.py
api/tests/unit_tests/controllers/console/workspace/rbac/test_roles.py
api/tests/unit_tests/controllers/inner_api/agent/test_tools.py
api/tests/unit_tests/core/agent/test_base_agent_runner.py
api/tests/unit_tests/core/app/apps/agent_app/test_runtime_request_builder.py
api/tests/unit_tests/core/workflow/nodes/agent_v2/test_runtime_request_builder.py
api/tests/unit_tests/core/workflow/test_node_runtime.py
api/tests/unit_tests/core/workflow/test_structured_output_compatibility.py
api/tests/unit_tests/services/enterprise/test_rbac_service.py
api/tests/unit_tests/repositories/workspace/test_workspace_repository.py
api/tests/unit_tests/services/test_agent_tool_inner_service.py
api/tests/unit_tests/services/test_vector_space_admission_service.py
api/tests/unit_tests/services/test_tidb_binding_service.py
)
for path in "${test_touchpoints[@]}"; do
add_test_target "$path"
done
required_test_scopes=(
/commands/
/configs/
/controllers/
/core/agent/
/core/app/apps/agent_app/
/core/workflow/
/dev/
/extensions/
/models/
/repositories/
/services/
/tasks/
)
for required_scope in "${required_test_scopes[@]}"; do
scope_found=false
for path in "${test_targets[@]}"; do
if [[ "$path" == *"$required_scope"* ]]; then
scope_found=true
break
fi
done
if [[ "$scope_found" != true ]]; then
echo "required Dify KnowledgeFS test scope is empty: $required_scope" >&2
exit 1
fi
done
if ((${#test_targets[@]} == 0)); then
echo "Dify KnowledgeFS unit test target set is empty" >&2
exit 1
fi
printf '%s\0' "${test_targets[@]}" > "$target_dir/unit-test-files"
- name: Lint Dify KnowledgeFS integration
working-directory: .
run: |
set -euo pipefail
targets=()
while IFS= read -r -d '' path; do
targets+=("$path")
done < "${RUNNER_TEMP:?}/knowledge-fs-ci-targets/production-files"
if ((${#targets[@]} == 0)); then
echo "Dify KnowledgeFS production target manifest is empty" >&2
exit 1
fi
uv run --project api --group vdb-qdrant --group vdb-elasticsearch --group vdb-weaviate --group vdb-tidb-on-qdrant --dev ruff format --check "${targets[@]}"
uv run --project api --group vdb-qdrant --group vdb-elasticsearch --group vdb-weaviate --group vdb-tidb-on-qdrant --dev ruff check "${targets[@]}"
- name: Type-check Dify KnowledgeFS integration
working-directory: .
run: |
set -euo pipefail
targets=()
while IFS= read -r -d '' path; do
targets+=("$path")
done < "${RUNNER_TEMP:?}/knowledge-fs-ci-targets/production-files"
if ((${#targets[@]} == 0)); then
echo "Dify KnowledgeFS production target manifest is empty" >&2
exit 1
fi
UV_NO_SYNC=1 PYREFLY_OUTPUT_FORMAT=github ./dev/pyrefly-check-local "${targets[@]}"
- name: Test Dify KnowledgeFS unit surface
working-directory: .
env:
COVERAGE_FILE: ${{ runner.temp }}/dify-knowledge-fs.coverage
run: |
set -euo pipefail
targets=()
while IFS= read -r -d '' path; do
targets+=("$path")
done < "${RUNNER_TEMP:?}/knowledge-fs-ci-targets/unit-test-files"
if ((${#targets[@]} == 0)); then
echo "Dify KnowledgeFS unit test target manifest is empty" >&2
exit 1
fi
uv run --project api --group vdb-qdrant --group vdb-elasticsearch --group vdb-weaviate --group vdb-tidb-on-qdrant --dev coverage run --branch --source=api -m pytest "${targets[@]}" --no-cov -q
- name: Enforce Dify KnowledgeFS focused coverage
working-directory: .
env:
COVERAGE_FILE: ${{ runner.temp }}/dify-knowledge-fs.coverage
KNOWLEDGE_FS_COVERAGE_BASE: >-
${{ github.event.pull_request.base.sha || github.event.merge_group.base_sha || github.event.before || '' }}
run: |
set -euo pipefail
report="${RUNNER_TEMP:?}/dify-knowledge-fs-coverage.json"
coverage_base="$KNOWLEDGE_FS_COVERAGE_BASE"
if [[ "$GITHUB_EVENT_NAME" == 'push' && "$GITHUB_REF" == 'refs/heads/deploy/konwledge' ]]; then
latest_merge="$(git rev-list --first-parent --merges --max-count=1 "${coverage_base}..HEAD")"
if [[ -n "$latest_merge" ]]; then
# Mainline syncs are already gated on main; cover deployment-branch
# changes made after the newest sync instead of the merged diff.
coverage_base="$latest_merge"
fi
fi
uv run --project api --group vdb-qdrant --group vdb-elasticsearch --group vdb-weaviate --group vdb-tidb-on-qdrant --dev coverage json --show-contexts -o "$report"
uv run --project api --group vdb-qdrant --group vdb-elasticsearch --group vdb-weaviate --group vdb-tidb-on-qdrant --dev python api/dev/check_knowledge_fs_coverage.py \
--coverage-json "$report" \
--glue-manifest "${RUNNER_TEMP:?}/knowledge-fs-ci-targets/glue-files" \
--base "$coverage_base" \
--minimum 90 \
--glue-minimum 90
- name: Verify Dify KnowledgeFS contract
working-directory: .
run: uv run --project api --group vdb-qdrant --group vdb-elasticsearch --group vdb-weaviate --group vdb-tidb-on-qdrant python api/dev/generate_knowledge_fs_contract.py --check
- name: Verify Dify Agent dependency lock
working-directory: .
run: uv lock --project dify-agent --check
- name: Install Dify Agent gate dependencies
working-directory: .
run: uv sync --project dify-agent --locked --dev
- name: Lint Dify Agent KnowledgeFS integration
working-directory: ./dify-agent
run: |
uv run --project . --dev ruff format --check \
src/dify_agent/layers/dify_core_tools/client.py \
tests/local/dify_agent/layers/dify_core_tools/test_client.py
uv run --project . --dev ruff check \
src/dify_agent/layers/dify_core_tools/client.py \
tests/local/dify_agent/layers/dify_core_tools/test_client.py
- name: Type-check Dify Agent KnowledgeFS integration
working-directory: ./dify-agent
run: >-
uv run --project . --dev pyrefly check --summary=none
src/dify_agent/layers/dify_core_tools/client.py
tests/local/dify_agent/layers/dify_core_tools/test_client.py
- name: Test Dify Agent KnowledgeFS integration
working-directory: ./dify-agent
run: >-
uv run --project . --dev python -m pytest
tests/local/dify_agent/layers/dify_core_tools/test_client.py
-q
skip:
name: Skip KnowledgeFS quality and contract gates
needs: check-changes
if: needs.check-changes.outputs.knowledge-fs != 'true' && github.event_name != 'workflow_dispatch'
runs-on: depot-ubuntu-24.04-4
steps:
- name: Report skipped KnowledgeFS checks
run: echo "No KnowledgeFS-related changes detected; skipping KnowledgeFS checks."
final:
name: KnowledgeFS CI
if: ${{ always() }}
needs:
- check-changes
- quality
- vector-backends
- skip
runs-on: depot-ubuntu-24.04-4
steps:
- name: Finalize KnowledgeFS CI status
env:
EVENT_NAME: ${{ github.event_name }}
KNOWLEDGE_FS_CHANGED: ${{ needs.check-changes.outputs.knowledge-fs }}
QUALITY_RESULT: ${{ needs.quality.result }}
VECTOR_RESULT: ${{ needs.vector-backends.result }}
SKIP_RESULT: ${{ needs.skip.result }}
run: |
if [[ "$EVENT_NAME" == 'workflow_dispatch' || "$KNOWLEDGE_FS_CHANGED" == 'true' ]]; then
if [[ "$QUALITY_RESULT" == 'success' && "$VECTOR_RESULT" == 'success' ]]; then
echo "Dify KnowledgeFS contract and integration checks ran successfully."
exit 0
fi
echo "Dify KnowledgeFS integration checks failed: quality=$QUALITY_RESULT vectors=$VECTOR_RESULT" >&2
exit 1
fi
if [[ "$SKIP_RESULT" == 'success' ]]; then
echo "KnowledgeFS checks were skipped because no related files changed."
exit 0
fi
echo "KnowledgeFS change detection or skip reporting failed with result: $SKIP_RESULT" >&2
exit 1
vector-backends:
name: Test native KnowledgeFS vector backends
needs: check-changes
if: needs.check-changes.outputs.knowledge-fs == 'true' || github.event_name == 'workflow_dispatch'
runs-on: depot-ubuntu-24.04-4
services:
qdrant:
image: qdrant/qdrant:v1.8.3
ports:
- 6333:6333
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:8.14.3
env:
discovery.type: single-node
xpack.security.enabled: 'false'
ES_JAVA_OPTS: '-Xms512m -Xmx512m'
ports:
- 9200:9200
options: >-
--health-cmd "curl -fsS http://localhost:9200/_cluster/health"
--health-interval 10s --health-timeout 5s --health-retries 30
weaviate:
image: cr.weaviate.io/semitechnologies/weaviate:1.39.2
env:
AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED: 'true'
PERSISTENCE_DATA_PATH: /var/lib/weaviate
DEFAULT_VECTORIZER_MODULE: none
CLUSTER_HOSTNAME: node1
ports:
- 8080:8080
- 50051:50051
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3
with:
enable-cache: true
python-version: '3.13'
cache-dependency-glob: api/uv.lock
- name: Install native vector SDKs
run: uv sync --project api --locked --dev --group vdb-qdrant --group vdb-elasticsearch --group vdb-weaviate
- name: Wait for Weaviate readiness
run: |
for attempt in $(seq 1 60); do
if curl -fsS http://localhost:8080/v1/.well-known/ready; then exit 0; fi
sleep 2
done
exit 1
- name: Exercise real write, retry, filtered search, isolation and deletion
env:
QDRANT_URL: http://localhost:6333
QDRANT_GRPC_ENABLED: 'false'
KNOWLEDGE_FS_VECTOR_INTEGRATION: '1'
DEPLOYMENT_EDITION: COMMUNITY
ELASTICSEARCH_HOST: localhost
ELASTICSEARCH_PORT: '9200'
WEAVIATE_ENDPOINT: http://localhost:8080
WEAVIATE_GRPC_ENDPOINT: grpc://localhost:50051
run: >-
uv run --project api --no-sync pytest
api/tests/e2e_tests/knowledge_fs_vector --no-cov -q