DeepFabric 4.4.1__tar.gz → 4.5.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {deepfabric-4.4.1 → deepfabric-4.5.1}/.gitignore +1 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/CLAUDE.md +2 -1
- {deepfabric-4.4.1 → deepfabric-4.5.1}/PKG-INFO +6 -3
- {deepfabric-4.4.1 → deepfabric-4.5.1}/README.md +5 -2
- deepfabric-4.5.1/assets/df-demo.gif +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/builders_agent.py +16 -4
- {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/cli.py +3 -3
- {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/evaluation/backends/__init__.py +2 -0
- deepfabric-4.5.1/deepfabric/evaluation/backends/llm_eval_backend.py +527 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/evaluation/backends/ollama_backend.py +3 -3
- {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/evaluation/backends/tool_call_parsers.py +7 -7
- {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/evaluation/backends/transformers_backend.py +73 -16
- {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/evaluation/evaluator.py +41 -7
- {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/evaluation/evaluators/builtin/tool_calling.py +13 -8
- {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/evaluation/inference.py +77 -5
- {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/evaluation/metrics.py +4 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/evaluation/reporters/cloud_reporter.py +1 -1
- {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/generator.py +4 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/hf_hub.py +1 -1
- {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/schemas.py +2 -2
- deepfabric-4.5.1/deepfabric/training/__init__.py +54 -0
- deepfabric-4.5.1/deepfabric/training/dataset_utils.py +223 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/docs/cli/index.md +6 -3
- deepfabric-4.5.1/docs/cli/upload-hf.md +89 -0
- deepfabric-4.5.1/docs/cli/upload-kaggle.md +82 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/docs/evaluation/index.md +20 -1
- {deepfabric-4.4.1 → deepfabric-4.5.1}/docs/evaluation/running.md +129 -8
- {deepfabric-4.4.1 → deepfabric-4.5.1}/docs/index.md +41 -23
- deepfabric-4.5.1/docs/training/dataset-preparation.md +193 -0
- deepfabric-4.5.1/docs/training/frameworks.md +489 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/docs/training/index.md +15 -6
- {deepfabric-4.4.1 → deepfabric-4.5.1}/docs/training/loading.md +30 -2
- {deepfabric-4.4.1 → deepfabric-4.5.1}/examples/agent-tools-multi.yaml +1 -1
- {deepfabric-4.4.1 → deepfabric-4.5.1}/examples/reasoning.yaml +3 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/examples/tools-sdk-examples/blender/spin-blender.yaml +5 -4
- {deepfabric-4.4.1 → deepfabric-4.5.1}/mkdocs.yml +2 -1
- deepfabric-4.5.1/notebooks/dataset-compatibility-check.ipynb +616 -0
- deepfabric-4.5.1/notebooks/evaluations.ipynb +161 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/pyproject.toml +1 -1
- {deepfabric-4.4.1 → deepfabric-4.5.1}/tests/unit/test_cli.py +1 -1
- deepfabric-4.5.1/tests/unit/test_llm_eval_backend.py +471 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/uv.lock +1 -1
- deepfabric-4.4.1/assets/df-demo.gif +0 -0
- deepfabric-4.4.1/deepfabric/training/__init__.py +0 -35
- deepfabric-4.4.1/docs/cli/upload.md +0 -153
- deepfabric-4.4.1/docs/training/frameworks.md +0 -155
- deepfabric-4.4.1/notebooks/trl_sft_training.ipynb +0 -602
- {deepfabric-4.4.1 → deepfabric-4.5.1}/.github/config.yml +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/.github/dependabot.yml +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/.github/workflows/docs.yml +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/.github/workflows/integration.yml +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/.github/workflows/publish.yml +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/.github/workflows/test.yml +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/.github/workflows/tools-sdk-docker.yml +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/LICENSE +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/Makefile +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/assets/logo-light-hols.png +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/assets/logo-light.png +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/assets/star.gif +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/coverage.xml +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/__init__.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/__main__.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/auth.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/builders.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/config.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/config_manager.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/constants.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/dataset_manager.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/error_codes.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/evaluation/__init__.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/evaluation/evaluators/__init__.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/evaluation/evaluators/base.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/evaluation/evaluators/builtin/__init__.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/evaluation/evaluators/registry.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/evaluation/parser.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/evaluation/reporters/__init__.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/evaluation/reporters/base.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/evaluation/reporters/file_reporter.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/evaluation/reporters/multi_reporter.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/exceptions.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/factory.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/graph.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/kaggle_hub.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/llm/__init__.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/llm/api_key_verifier.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/llm/client.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/llm/errors.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/llm/rate_limit_config.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/llm/rate_limit_detector.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/llm/retry_handler.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/metrics.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/progress.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/prompts.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/spin/__init__.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/spin/client.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/spin/models.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/stream_simulator.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/tools/__init__.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/tools/defaults.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/tools/loader.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/tools/mcp_client.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/topic_manager.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/topic_model.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/training/api_key_prompt.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/training/callback.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/training/metrics_sender.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/tree.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/tui.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/update_checker.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/utils.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/validation.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/docs/api/config.md +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/docs/api/generator.md +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/docs/api/graph.md +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/docs/api/index.md +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/docs/api/tree.md +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/docs/cli/format.md +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/docs/cli/generate.md +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/docs/cli/import-tools.md +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/docs/cli/validate.md +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/docs/cli/visualize.md +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/docs/dataset-generation/agent.md +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/docs/dataset-generation/basic.md +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/docs/dataset-generation/configuration.md +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/docs/dataset-generation/index.md +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/docs/dataset-generation/rate-limiting.md +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/docs/dataset-generation/reasoning.md +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/docs/evaluation/metrics.md +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/docs/getting-started/index.md +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/docs/images/logo-light.png +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/docs/tools/custom.md +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/docs/tools/index.md +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/docs/tools/mcp.md +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/docs/tools/mock.md +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/docs/tools/spin.md +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/docs/tools/vfs.md +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/docs/training/chat-templates.md +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/examples/agent-tools-single.yaml +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/examples/basic-anthropic.yaml +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/examples/basic-gemini.yaml +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/examples/basic-graph.yaml +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/examples/basic-ollama.yaml +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/examples/basic-openai.yaml +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/examples/basic-openrouter.yaml +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/examples/basic-tree.yaml +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/examples/coding-agent.yaml +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/examples/complete.yaml +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/examples/custom-tools.yaml +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/examples/github-mock-tools.yaml +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/examples/spin-vfs-tools.yaml +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/examples/tools-sdk-examples/blender/blender-mock-data.json +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/examples/tools-sdk-examples/blender/load-blender-mock-data.sh +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/examples/tools-sdk-examples/figma/figma-mock-data.json +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/examples/tools-sdk-examples/figma/load-figma-mock-data.sh +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/examples/tools-sdk-examples/figma/spin-figma.yaml +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/examples/tools-sdk-examples/github/github-mock-data.json +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/examples/tools-sdk-examples/github/load-github-mock-data.sh +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/examples/tools-sdk-examples/github/spin-github-tools.yaml +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/examples/tools-sdk-examples/kubernetes/kubernetes-mock-data.json +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/examples/tools-sdk-examples/kubernetes/load-kubernetes-mock-data.sh +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/examples/tools-sdk-examples/kubernetes/spin-kubernetes.yaml +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/examples/tools-sdk-examples/kubernetes/tools.json +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/misc/test_update_manual.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/test-run/01-alpaca.txt +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/test-run/01-chatml.txt +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/test-run/01-grpo.txt +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/test-run/01-xlam_v2.txt +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/test-run/02-trl2.txt +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/test-run/02-xlam_v2.txt +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/test-run/04-agent-tool-conversations.jsnl +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/test-run/04-single-agent-tools +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/tests/__init__.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/tests/integration/__init__.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/tests/integration/test_tree_integration.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/tests/unit/__init__.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/tests/unit/conftest.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/tests/unit/test_api_key_validation.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/tests/unit/test_config.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/tests/unit/test_error_codes.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/tests/unit/test_gemini_schema.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/tests/unit/test_generator.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/tests/unit/test_hf_hub.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/tests/unit/test_kaggle_hub.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/tests/unit/test_modular_config.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/tests/unit/test_rate_limiting.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/tests/unit/test_schemas.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/tests/unit/test_tool_call_parsers.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/tests/unit/test_topic_graph.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/tests/unit/test_training_callback.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/tests/unit/test_update_checker.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/tools/extract_messages.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/tools/function.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/tools/yaml_to_openai_tools.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/tools-sdk/.dockerignore +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/tools-sdk/Dockerfile +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/tools-sdk/README.md +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/tools-sdk/components/github/README.md +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/tools-sdk/components/github/app.py +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/tools-sdk/components/github/github.wasm +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/tools-sdk/components/github/pyproject.toml +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/tools-sdk/components/github/requirements.txt +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/tools-sdk/components/github/spin.toml +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/tools-sdk/components/github/uv.lock +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/tools-sdk/components/mock/Cargo.lock +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/tools-sdk/components/mock/Cargo.toml +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/tools-sdk/components/mock/README.md +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/tools-sdk/components/mock/src/lib.rs +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/tools-sdk/components/vfs/Cargo.lock +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/tools-sdk/components/vfs/Cargo.toml +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/tools-sdk/components/vfs/src/lib.rs +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/tools-sdk/docker-compose.yaml +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/tools-sdk/runtime-config.toml +0 -0
- {deepfabric-4.4.1 → deepfabric-4.5.1}/tools-sdk/spin.toml +0 -0
|
@@ -97,4 +97,5 @@ deepfabric start config.yaml --model gpt-4 --temperature 0.8 --hf-repo user/data
|
|
|
97
97
|
- Bandit for security analysis
|
|
98
98
|
- Python 3.11+ required
|
|
99
99
|
- Google-style docstrings preferred
|
|
100
|
-
- do not place imports anywhere but the top of the file
|
|
100
|
+
- do not place imports anywhere but the top of the file
|
|
101
|
+
- When updating `docs/` documentation, if new Markdown files are added or removed, consider updating `mkdocs.yml`.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: DeepFabric
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.5.1
|
|
4
4
|
Summary: Curate High Quality Datasets, Train, Evaluate and Ship
|
|
5
5
|
Author-email: Luke Hinds <luke@alwaysfurther.ai>
|
|
6
6
|
License-File: LICENSE
|
|
@@ -77,6 +77,9 @@ Description-Content-Type: text/markdown
|
|
|
77
77
|
<a href="https://discord.gg/pPcjYzGvbS">
|
|
78
78
|
<img src="https://img.shields.io/discord/1384081906773131274?color=7289da&label=Discord&logo=discord&logoColor=white" alt="Discord"/>
|
|
79
79
|
</a>
|
|
80
|
+
<a href="https://www.reddit.com/r/deepfabric/">
|
|
81
|
+
<img src="https://img.shields.io/badge/Reddit-r%2Fdeepfabric-FF4500?logo=reddit&logoColor=white" alt="Reddit"/>
|
|
82
|
+
</a>
|
|
80
83
|
</p>
|
|
81
84
|
</div>
|
|
82
85
|
|
|
@@ -86,7 +89,7 @@ What sets DeepFabric apart from other dataset generation tools is its ability to
|
|
|
86
89
|
|
|
87
90
|
<img src="/assets/df-demo.gif" width="100%" height="100%"/>
|
|
88
91
|
|
|
89
|
-
Constrained decoding and response validation, along with real tool executions within isolated webassembly environments, ensure that generated samples strictly adhere to structured schema, variable constraints, and execution correctness, ensuring datasets have exact syntax and structure for use in model training pipelines. Tool definations can be either directly imported from MCP (Model Context Protocol) server schemas and automatically mocked, real life interfaces along with a standard set of common tools (`list_files()`, 'read_file()` etc)
|
|
92
|
+
Constrained decoding and response validation, along with real tool executions within isolated webassembly environments, ensure that generated samples strictly adhere to structured schema, variable constraints, and execution correctness, ensuring datasets have exact syntax and structure for use in model training pipelines. Tool definations can be either directly imported from MCP (Model Context Protocol) server schemas and automatically mocked, real life interfaces along with a standard set of common tools (`list_files()`, `'read_file()` etc)
|
|
90
93
|
|
|
91
94
|
Once your dataset is generated, it can be automatically uploaded to Hugging Face and directly imported into popular training frameworks like TRL, Unsloth, and Axolotl.
|
|
92
95
|
|
|
@@ -215,7 +218,7 @@ deepfabric generate config.yaml --output-save-as dataset.jsonl
|
|
|
215
218
|
Or upload to HuggingFace Hub:
|
|
216
219
|
|
|
217
220
|
```bash
|
|
218
|
-
deepfabric upload dataset.jsonl --repo your-username/my-dataset
|
|
221
|
+
deepfabric upload-hf dataset.jsonl --repo your-username/my-dataset
|
|
219
222
|
```
|
|
220
223
|
|
|
221
224
|
### 2. Load and Split for Training
|
|
@@ -33,6 +33,9 @@
|
|
|
33
33
|
<a href="https://discord.gg/pPcjYzGvbS">
|
|
34
34
|
<img src="https://img.shields.io/discord/1384081906773131274?color=7289da&label=Discord&logo=discord&logoColor=white" alt="Discord"/>
|
|
35
35
|
</a>
|
|
36
|
+
<a href="https://www.reddit.com/r/deepfabric/">
|
|
37
|
+
<img src="https://img.shields.io/badge/Reddit-r%2Fdeepfabric-FF4500?logo=reddit&logoColor=white" alt="Reddit"/>
|
|
38
|
+
</a>
|
|
36
39
|
</p>
|
|
37
40
|
</div>
|
|
38
41
|
|
|
@@ -42,7 +45,7 @@ What sets DeepFabric apart from other dataset generation tools is its ability to
|
|
|
42
45
|
|
|
43
46
|
<img src="/assets/df-demo.gif" width="100%" height="100%"/>
|
|
44
47
|
|
|
45
|
-
Constrained decoding and response validation, along with real tool executions within isolated webassembly environments, ensure that generated samples strictly adhere to structured schema, variable constraints, and execution correctness, ensuring datasets have exact syntax and structure for use in model training pipelines. Tool definations can be either directly imported from MCP (Model Context Protocol) server schemas and automatically mocked, real life interfaces along with a standard set of common tools (`list_files()`, 'read_file()` etc)
|
|
48
|
+
Constrained decoding and response validation, along with real tool executions within isolated webassembly environments, ensure that generated samples strictly adhere to structured schema, variable constraints, and execution correctness, ensuring datasets have exact syntax and structure for use in model training pipelines. Tool definations can be either directly imported from MCP (Model Context Protocol) server schemas and automatically mocked, real life interfaces along with a standard set of common tools (`list_files()`, `'read_file()` etc)
|
|
46
49
|
|
|
47
50
|
Once your dataset is generated, it can be automatically uploaded to Hugging Face and directly imported into popular training frameworks like TRL, Unsloth, and Axolotl.
|
|
48
51
|
|
|
@@ -171,7 +174,7 @@ deepfabric generate config.yaml --output-save-as dataset.jsonl
|
|
|
171
174
|
Or upload to HuggingFace Hub:
|
|
172
175
|
|
|
173
176
|
```bash
|
|
174
|
-
deepfabric upload dataset.jsonl --repo your-username/my-dataset
|
|
177
|
+
deepfabric upload-hf dataset.jsonl --repo your-username/my-dataset
|
|
175
178
|
```
|
|
176
179
|
|
|
177
180
|
### 2. Load and Split for Training
|
|
Binary file
|
|
@@ -763,8 +763,14 @@ Remember: You have access to the tools listed above and have used them in this c
|
|
|
763
763
|
# Insert system message if configured
|
|
764
764
|
self._insert_system_message_if_configured(messages)
|
|
765
765
|
|
|
766
|
-
# Convert tools to OpenAI format
|
|
767
|
-
|
|
766
|
+
# Convert tools to OpenAI format, filtering based on inclusion strategy
|
|
767
|
+
if self.config.tool_inclusion_strategy == "used_only" and tool_results:
|
|
768
|
+
used_names = {te.function_name for te in tool_results}
|
|
769
|
+
tools_openai = [
|
|
770
|
+
tool.to_openai() for tool in self.tool_registry.tools if tool.name in used_names
|
|
771
|
+
]
|
|
772
|
+
else:
|
|
773
|
+
tools_openai = [tool.to_openai() for tool in self.tool_registry.tools]
|
|
768
774
|
|
|
769
775
|
return Conversation(
|
|
770
776
|
messages=messages,
|
|
@@ -1291,8 +1297,14 @@ Is the user's original task/goal from the scenario fully completed?
|
|
|
1291
1297
|
# Insert system message if configured
|
|
1292
1298
|
self._insert_system_message_if_configured(messages)
|
|
1293
1299
|
|
|
1294
|
-
# Convert tools to OpenAI format
|
|
1295
|
-
|
|
1300
|
+
# Convert tools to OpenAI format, filtering based on inclusion strategy
|
|
1301
|
+
if self.config.tool_inclusion_strategy == "used_only" and all_executions:
|
|
1302
|
+
used_names = {te.function_name for te in all_executions}
|
|
1303
|
+
tools_openai = [
|
|
1304
|
+
tool.to_openai() for tool in self.tool_registry.tools if tool.name in used_names
|
|
1305
|
+
]
|
|
1306
|
+
else:
|
|
1307
|
+
tools_openai = [tool.to_openai() for tool in self.tool_registry.tools]
|
|
1296
1308
|
|
|
1297
1309
|
return Conversation(
|
|
1298
1310
|
messages=messages,
|
|
@@ -557,7 +557,7 @@ def generate( # noqa: PLR0913
|
|
|
557
557
|
sys.exit(1)
|
|
558
558
|
|
|
559
559
|
|
|
560
|
-
@cli.command()
|
|
560
|
+
@cli.command("upload-hf")
|
|
561
561
|
@click.argument("dataset_file", type=click.Path(exists=True))
|
|
562
562
|
@click.option(
|
|
563
563
|
"--repo",
|
|
@@ -573,14 +573,14 @@ def generate( # noqa: PLR0913
|
|
|
573
573
|
multiple=True,
|
|
574
574
|
help="Tags for the dataset (can be specified multiple times)",
|
|
575
575
|
)
|
|
576
|
-
def
|
|
576
|
+
def upload_hf(
|
|
577
577
|
dataset_file: str,
|
|
578
578
|
repo: str,
|
|
579
579
|
token: str | None = None,
|
|
580
580
|
tags: list[str] | None = None,
|
|
581
581
|
) -> None:
|
|
582
582
|
"""Upload a dataset to Hugging Face Hub."""
|
|
583
|
-
trace("
|
|
583
|
+
trace("cli_upload_hf", {"has_tags": len(tags) > 0 if tags else False})
|
|
584
584
|
|
|
585
585
|
try:
|
|
586
586
|
# Get token from CLI arg or env var
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""Inference backend implementations."""
|
|
2
2
|
|
|
3
|
+
from .llm_eval_backend import LLMEvalBackend
|
|
3
4
|
from .ollama_backend import OllamaBackend
|
|
4
5
|
from .tool_call_parsers import (
|
|
5
6
|
GenericToolCallParser,
|
|
@@ -18,6 +19,7 @@ from .transformers_backend import TransformersBackend
|
|
|
18
19
|
__all__ = [
|
|
19
20
|
"TransformersBackend",
|
|
20
21
|
"OllamaBackend",
|
|
22
|
+
"LLMEvalBackend",
|
|
21
23
|
# Tool call parsers
|
|
22
24
|
"ToolCallParser",
|
|
23
25
|
"ToolCallParserRegistry",
|
|
@@ -0,0 +1,527 @@
|
|
|
1
|
+
"""LLM Evaluation Backend for cloud providers.
|
|
2
|
+
|
|
3
|
+
Supports OpenAI, Anthropic, Gemini, and OpenRouter for tool calling evaluation.
|
|
4
|
+
Uses async clients internally with sync wrapper for compatibility with Evaluator.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import json
|
|
9
|
+
import logging
|
|
10
|
+
import os
|
|
11
|
+
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from deepfabric.evaluation.inference import (
|
|
15
|
+
InferenceBackend,
|
|
16
|
+
InferenceConfig,
|
|
17
|
+
ModelResponse,
|
|
18
|
+
)
|
|
19
|
+
from deepfabric.llm.errors import handle_provider_error
|
|
20
|
+
from deepfabric.llm.rate_limit_config import (
|
|
21
|
+
RateLimitConfig,
|
|
22
|
+
create_rate_limit_config,
|
|
23
|
+
get_default_rate_limit_config,
|
|
24
|
+
)
|
|
25
|
+
from deepfabric.llm.retry_handler import RetryHandler
|
|
26
|
+
from deepfabric.schemas import ToolDefinition
|
|
27
|
+
|
|
28
|
+
logger = logging.getLogger(__name__)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class LLMEvalBackend(InferenceBackend):
|
|
32
|
+
"""Inference backend using cloud LLM providers for evaluation.
|
|
33
|
+
|
|
34
|
+
Supports:
|
|
35
|
+
- OpenAI (GPT-4, GPT-4o, etc.)
|
|
36
|
+
- Anthropic (Claude models)
|
|
37
|
+
- Gemini (gemini-2.0-flash, etc.)
|
|
38
|
+
- OpenRouter (OpenAI-compatible API)
|
|
39
|
+
|
|
40
|
+
Uses async clients internally with sync wrapper for compatibility.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def __init__(self, config: InferenceConfig) -> None:
|
|
44
|
+
"""Initialize LLM evaluation backend.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
config: Inference configuration with provider and model details
|
|
48
|
+
"""
|
|
49
|
+
super().__init__(config)
|
|
50
|
+
|
|
51
|
+
if config.provider is None:
|
|
52
|
+
msg = "provider must be specified for LLM backend"
|
|
53
|
+
raise ValueError(msg)
|
|
54
|
+
|
|
55
|
+
self.provider = config.provider
|
|
56
|
+
self.model_name = config.model
|
|
57
|
+
|
|
58
|
+
# Initialize rate limiting
|
|
59
|
+
self.rate_limit_config: RateLimitConfig
|
|
60
|
+
if config.rate_limit_config:
|
|
61
|
+
self.rate_limit_config = create_rate_limit_config(
|
|
62
|
+
self.provider, config.rate_limit_config
|
|
63
|
+
)
|
|
64
|
+
else:
|
|
65
|
+
self.rate_limit_config = get_default_rate_limit_config(self.provider)
|
|
66
|
+
|
|
67
|
+
self.retry_handler = RetryHandler(self.rate_limit_config, self.provider)
|
|
68
|
+
|
|
69
|
+
# Initialize provider-specific async client
|
|
70
|
+
self._client = self._create_client()
|
|
71
|
+
|
|
72
|
+
def _create_client(self) -> Any:
|
|
73
|
+
"""Create the appropriate async client for the provider."""
|
|
74
|
+
client_creators = {
|
|
75
|
+
"openai": self._create_openai_client,
|
|
76
|
+
"anthropic": self._create_anthropic_client,
|
|
77
|
+
"gemini": self._create_gemini_client,
|
|
78
|
+
"openrouter": self._create_openrouter_client,
|
|
79
|
+
}
|
|
80
|
+
if creator := client_creators.get(self.provider):
|
|
81
|
+
return creator()
|
|
82
|
+
msg = f"Unsupported provider: {self.provider}"
|
|
83
|
+
raise ValueError(msg)
|
|
84
|
+
|
|
85
|
+
def _create_openai_client(self) -> Any:
|
|
86
|
+
"""Create async OpenAI client."""
|
|
87
|
+
import openai # noqa: PLC0415
|
|
88
|
+
|
|
89
|
+
api_key = self.config.api_key or os.getenv("OPENAI_API_KEY")
|
|
90
|
+
if not api_key:
|
|
91
|
+
msg = "OPENAI_API_KEY environment variable is not set"
|
|
92
|
+
raise ValueError(msg)
|
|
93
|
+
|
|
94
|
+
kwargs: dict[str, Any] = {"api_key": api_key}
|
|
95
|
+
if self.config.base_url:
|
|
96
|
+
kwargs["base_url"] = self.config.base_url
|
|
97
|
+
|
|
98
|
+
return openai.AsyncOpenAI(**kwargs)
|
|
99
|
+
|
|
100
|
+
def _create_anthropic_client(self) -> Any:
|
|
101
|
+
"""Create async Anthropic client."""
|
|
102
|
+
import anthropic # noqa: PLC0415
|
|
103
|
+
|
|
104
|
+
api_key = self.config.api_key or os.getenv("ANTHROPIC_API_KEY")
|
|
105
|
+
if not api_key:
|
|
106
|
+
msg = "ANTHROPIC_API_KEY environment variable is not set"
|
|
107
|
+
raise ValueError(msg)
|
|
108
|
+
|
|
109
|
+
return anthropic.AsyncAnthropic(api_key=api_key)
|
|
110
|
+
|
|
111
|
+
def _create_gemini_client(self) -> Any:
|
|
112
|
+
"""Create Gemini client (uses aio namespace for async)."""
|
|
113
|
+
from google import genai # noqa: PLC0415
|
|
114
|
+
|
|
115
|
+
api_key = self.config.api_key or os.getenv("GOOGLE_API_KEY") or os.getenv("GEMINI_API_KEY")
|
|
116
|
+
if not api_key:
|
|
117
|
+
msg = "GOOGLE_API_KEY or GEMINI_API_KEY environment variable is not set"
|
|
118
|
+
raise ValueError(msg)
|
|
119
|
+
|
|
120
|
+
return genai.Client(api_key=api_key)
|
|
121
|
+
|
|
122
|
+
def _create_openrouter_client(self) -> Any:
|
|
123
|
+
"""Create async OpenRouter client (OpenAI-compatible)."""
|
|
124
|
+
import openai # noqa: PLC0415
|
|
125
|
+
|
|
126
|
+
api_key = self.config.api_key or os.getenv("OPENROUTER_API_KEY")
|
|
127
|
+
if not api_key:
|
|
128
|
+
msg = "OPENROUTER_API_KEY environment variable is not set"
|
|
129
|
+
raise ValueError(msg)
|
|
130
|
+
|
|
131
|
+
base_url = self.config.base_url or "https://openrouter.ai/api/v1"
|
|
132
|
+
return openai.AsyncOpenAI(api_key=api_key, base_url=base_url)
|
|
133
|
+
|
|
134
|
+
def generate(
|
|
135
|
+
self,
|
|
136
|
+
messages: list[dict[str, str]],
|
|
137
|
+
tools: list[ToolDefinition] | None = None,
|
|
138
|
+
) -> ModelResponse:
|
|
139
|
+
"""Generate response with optional tool calling (sync wrapper).
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
messages: List of message dicts with 'role' and 'content'
|
|
143
|
+
tools: Optional list of available tools for function calling
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
ModelResponse with generated content and parsed tool calls
|
|
147
|
+
"""
|
|
148
|
+
return asyncio.run(self.generate_async(messages, tools))
|
|
149
|
+
|
|
150
|
+
async def generate_async(
|
|
151
|
+
self,
|
|
152
|
+
messages: list[dict[str, str]],
|
|
153
|
+
tools: list[ToolDefinition] | None = None,
|
|
154
|
+
) -> ModelResponse:
|
|
155
|
+
"""Generate response with optional tool calling (async).
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
messages: List of message dicts with 'role' and 'content'
|
|
159
|
+
tools: Optional list of available tools for function calling
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
ModelResponse with generated content and parsed tool calls
|
|
163
|
+
"""
|
|
164
|
+
return await self._generate_with_retry(messages, tools)
|
|
165
|
+
|
|
166
|
+
async def _generate_with_retry(
|
|
167
|
+
self,
|
|
168
|
+
messages: list[dict[str, str]],
|
|
169
|
+
tools: list[ToolDefinition] | None = None,
|
|
170
|
+
) -> ModelResponse:
|
|
171
|
+
"""Generate with retry logic."""
|
|
172
|
+
attempt = 0
|
|
173
|
+
max_retries = self.rate_limit_config.max_retries
|
|
174
|
+
|
|
175
|
+
while attempt <= max_retries:
|
|
176
|
+
try:
|
|
177
|
+
return await self._do_generate(messages, tools)
|
|
178
|
+
except Exception as e:
|
|
179
|
+
if not self.retry_handler.should_retry(e):
|
|
180
|
+
raise handle_provider_error(e, self.provider, self.model_name) from e
|
|
181
|
+
|
|
182
|
+
if attempt >= max_retries:
|
|
183
|
+
self.retry_handler.on_giveup_handler({"exception": e, "tries": attempt + 1})
|
|
184
|
+
raise handle_provider_error(e, self.provider, self.model_name) from e
|
|
185
|
+
|
|
186
|
+
delay = self.retry_handler.calculate_delay(attempt, e)
|
|
187
|
+
self.retry_handler.on_backoff_handler(
|
|
188
|
+
{
|
|
189
|
+
"exception": e,
|
|
190
|
+
"wait": delay,
|
|
191
|
+
"tries": attempt + 1,
|
|
192
|
+
}
|
|
193
|
+
)
|
|
194
|
+
await asyncio.sleep(delay)
|
|
195
|
+
attempt += 1
|
|
196
|
+
|
|
197
|
+
msg = "Unexpected state in retry logic"
|
|
198
|
+
raise RuntimeError(msg)
|
|
199
|
+
|
|
200
|
+
async def _do_generate(
|
|
201
|
+
self,
|
|
202
|
+
messages: list[dict[str, str]],
|
|
203
|
+
tools: list[ToolDefinition] | None = None,
|
|
204
|
+
) -> ModelResponse:
|
|
205
|
+
"""Execute generation based on provider."""
|
|
206
|
+
generators = {
|
|
207
|
+
"openai": self._generate_openai_async,
|
|
208
|
+
"openrouter": self._generate_openai_async,
|
|
209
|
+
"anthropic": self._generate_anthropic_async,
|
|
210
|
+
"gemini": self._generate_gemini_async,
|
|
211
|
+
}
|
|
212
|
+
if generator := generators.get(self.provider):
|
|
213
|
+
return await generator(messages, tools)
|
|
214
|
+
msg = f"Unsupported provider: {self.provider}"
|
|
215
|
+
raise ValueError(msg)
|
|
216
|
+
|
|
217
|
+
async def _generate_openai_async(
|
|
218
|
+
self,
|
|
219
|
+
messages: list[dict[str, str]],
|
|
220
|
+
tools: list[ToolDefinition] | None = None,
|
|
221
|
+
) -> ModelResponse:
|
|
222
|
+
"""Generate using OpenAI/OpenRouter API."""
|
|
223
|
+
kwargs: dict[str, Any] = {
|
|
224
|
+
"model": self.model_name,
|
|
225
|
+
"messages": messages,
|
|
226
|
+
"temperature": self.config.temperature,
|
|
227
|
+
"max_tokens": self.config.max_tokens,
|
|
228
|
+
"top_p": self.config.top_p,
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if tools:
|
|
232
|
+
kwargs["tools"] = [tool.to_openai() for tool in tools]
|
|
233
|
+
kwargs["tool_choice"] = "auto"
|
|
234
|
+
|
|
235
|
+
response = await self._client.chat.completions.create(**kwargs)
|
|
236
|
+
message = response.choices[0].message
|
|
237
|
+
|
|
238
|
+
# Parse tool calls
|
|
239
|
+
tool_call = None
|
|
240
|
+
tool_calls = None
|
|
241
|
+
if message.tool_calls:
|
|
242
|
+
tool_calls = []
|
|
243
|
+
for tc in message.tool_calls:
|
|
244
|
+
# Parse arguments from JSON string
|
|
245
|
+
try:
|
|
246
|
+
args = json.loads(tc.function.arguments)
|
|
247
|
+
except json.JSONDecodeError as e:
|
|
248
|
+
logger.warning(
|
|
249
|
+
"Failed to parse tool call arguments as JSON: %s (%s)",
|
|
250
|
+
tc.function.arguments,
|
|
251
|
+
e,
|
|
252
|
+
)
|
|
253
|
+
args = {}
|
|
254
|
+
|
|
255
|
+
parsed = {
|
|
256
|
+
"name": tc.function.name,
|
|
257
|
+
"arguments": args,
|
|
258
|
+
}
|
|
259
|
+
tool_calls.append(parsed)
|
|
260
|
+
tool_call = tool_calls[0] if tool_calls else None
|
|
261
|
+
|
|
262
|
+
return ModelResponse(
|
|
263
|
+
content=message.content or "",
|
|
264
|
+
tool_call=tool_call,
|
|
265
|
+
tool_calls=tool_calls,
|
|
266
|
+
raw_output=message.content or "",
|
|
267
|
+
finish_reason=response.choices[0].finish_reason,
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
async def _generate_anthropic_async(
|
|
271
|
+
self,
|
|
272
|
+
messages: list[dict[str, str]],
|
|
273
|
+
tools: list[ToolDefinition] | None = None,
|
|
274
|
+
) -> ModelResponse:
|
|
275
|
+
"""Generate using Anthropic API."""
|
|
276
|
+
# Convert messages to Anthropic format (system message separate)
|
|
277
|
+
system_message = None
|
|
278
|
+
anthropic_messages = []
|
|
279
|
+
for msg in messages:
|
|
280
|
+
if msg["role"] == "system":
|
|
281
|
+
system_message = msg["content"]
|
|
282
|
+
else:
|
|
283
|
+
anthropic_messages.append(
|
|
284
|
+
{
|
|
285
|
+
"role": msg["role"],
|
|
286
|
+
"content": msg["content"],
|
|
287
|
+
}
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
# Anthropic doesn't allow both temperature and top_p together
|
|
291
|
+
# Use temperature only (the more commonly configured parameter)
|
|
292
|
+
kwargs: dict[str, Any] = {
|
|
293
|
+
"model": self.model_name,
|
|
294
|
+
"messages": anthropic_messages,
|
|
295
|
+
"max_tokens": self.config.max_tokens,
|
|
296
|
+
"temperature": self.config.temperature,
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if system_message:
|
|
300
|
+
kwargs["system"] = system_message
|
|
301
|
+
|
|
302
|
+
if tools:
|
|
303
|
+
kwargs["tools"] = [self._convert_tool_to_anthropic(tool) for tool in tools]
|
|
304
|
+
|
|
305
|
+
response = await self._client.messages.create(**kwargs)
|
|
306
|
+
|
|
307
|
+
# Parse response - Anthropic uses content blocks
|
|
308
|
+
content = ""
|
|
309
|
+
tool_call = None
|
|
310
|
+
tool_calls: list[dict[str, Any]] = []
|
|
311
|
+
|
|
312
|
+
for block in response.content:
|
|
313
|
+
if block.type == "text":
|
|
314
|
+
content += block.text
|
|
315
|
+
elif block.type == "tool_use":
|
|
316
|
+
parsed = {
|
|
317
|
+
"name": block.name,
|
|
318
|
+
"arguments": block.input,
|
|
319
|
+
}
|
|
320
|
+
tool_calls.append(parsed)
|
|
321
|
+
|
|
322
|
+
tool_call = tool_calls[0] if tool_calls else None
|
|
323
|
+
|
|
324
|
+
return ModelResponse(
|
|
325
|
+
content=content,
|
|
326
|
+
tool_call=tool_call,
|
|
327
|
+
tool_calls=tool_calls if tool_calls else None,
|
|
328
|
+
raw_output=content,
|
|
329
|
+
finish_reason=response.stop_reason,
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
def _convert_tool_to_anthropic(self, tool: ToolDefinition) -> dict[str, Any]:
|
|
333
|
+
"""Convert ToolDefinition to Anthropic tool format."""
|
|
334
|
+
openai_format = tool.to_openai()
|
|
335
|
+
func = openai_format["function"]
|
|
336
|
+
|
|
337
|
+
return {
|
|
338
|
+
"name": func["name"],
|
|
339
|
+
"description": func["description"],
|
|
340
|
+
"input_schema": func["parameters"],
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
async def _generate_gemini_async(
|
|
344
|
+
self,
|
|
345
|
+
messages: list[dict[str, str]],
|
|
346
|
+
tools: list[ToolDefinition] | None = None,
|
|
347
|
+
) -> ModelResponse:
|
|
348
|
+
"""Generate using Gemini API."""
|
|
349
|
+
from google.genai import types # noqa: PLC0415
|
|
350
|
+
|
|
351
|
+
# Convert messages to Gemini format
|
|
352
|
+
gemini_contents: list[types.Content] = []
|
|
353
|
+
system_instruction = None
|
|
354
|
+
|
|
355
|
+
for msg in messages:
|
|
356
|
+
role = msg["role"]
|
|
357
|
+
if role == "system":
|
|
358
|
+
# Gemini uses system_instruction parameter
|
|
359
|
+
system_instruction = msg["content"]
|
|
360
|
+
elif role == "assistant":
|
|
361
|
+
gemini_contents.append(
|
|
362
|
+
types.Content(role="model", parts=[types.Part(text=msg["content"])])
|
|
363
|
+
)
|
|
364
|
+
else:
|
|
365
|
+
gemini_contents.append(
|
|
366
|
+
types.Content(role="user", parts=[types.Part(text=msg["content"])])
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
# Prepare tools for Gemini
|
|
370
|
+
gemini_tools = None
|
|
371
|
+
if tools:
|
|
372
|
+
function_declarations = []
|
|
373
|
+
for tool in tools:
|
|
374
|
+
openai_format = tool.to_openai()
|
|
375
|
+
func = openai_format["function"]
|
|
376
|
+
|
|
377
|
+
# Gemini uses slightly different schema format
|
|
378
|
+
params = self._convert_schema_for_gemini(func["parameters"])
|
|
379
|
+
|
|
380
|
+
function_declarations.append(
|
|
381
|
+
types.FunctionDeclaration(
|
|
382
|
+
name=func["name"],
|
|
383
|
+
description=func["description"],
|
|
384
|
+
parameters=params,
|
|
385
|
+
)
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
gemini_tools = [types.Tool(function_declarations=function_declarations)]
|
|
389
|
+
|
|
390
|
+
# Configure generation
|
|
391
|
+
generation_config = types.GenerateContentConfig(
|
|
392
|
+
temperature=self.config.temperature,
|
|
393
|
+
max_output_tokens=self.config.max_tokens,
|
|
394
|
+
top_p=self.config.top_p,
|
|
395
|
+
system_instruction=system_instruction,
|
|
396
|
+
tools=gemini_tools,
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
response = await self._client.aio.models.generate_content(
|
|
400
|
+
model=self.model_name,
|
|
401
|
+
contents=gemini_contents,
|
|
402
|
+
config=generation_config,
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
# Parse response
|
|
406
|
+
content = ""
|
|
407
|
+
tool_call = None
|
|
408
|
+
tool_calls: list[dict[str, Any]] = []
|
|
409
|
+
|
|
410
|
+
if response.candidates:
|
|
411
|
+
for part in response.candidates[0].content.parts:
|
|
412
|
+
if part.text:
|
|
413
|
+
content += part.text
|
|
414
|
+
elif part.function_call:
|
|
415
|
+
fc = part.function_call
|
|
416
|
+
parsed = {
|
|
417
|
+
"name": fc.name,
|
|
418
|
+
"arguments": dict(fc.args) if fc.args else {},
|
|
419
|
+
}
|
|
420
|
+
tool_calls.append(parsed)
|
|
421
|
+
|
|
422
|
+
tool_call = tool_calls[0] if tool_calls else None
|
|
423
|
+
finish_reason = response.candidates[0].finish_reason.name if response.candidates else None
|
|
424
|
+
|
|
425
|
+
return ModelResponse(
|
|
426
|
+
content=content,
|
|
427
|
+
tool_call=tool_call,
|
|
428
|
+
tool_calls=tool_calls if tool_calls else None,
|
|
429
|
+
raw_output=content,
|
|
430
|
+
finish_reason=finish_reason,
|
|
431
|
+
)
|
|
432
|
+
|
|
433
|
+
def _convert_schema_for_gemini(self, schema: dict[str, Any]) -> dict[str, Any]:
|
|
434
|
+
"""Convert JSON Schema to Gemini-compatible format.
|
|
435
|
+
|
|
436
|
+
Gemini has specific requirements:
|
|
437
|
+
- Does not support additionalProperties
|
|
438
|
+
- Requires 'items' field for array types
|
|
439
|
+
- Handles nested schemas in anyOf, oneOf, allOf
|
|
440
|
+
"""
|
|
441
|
+
if not isinstance(schema, dict):
|
|
442
|
+
return schema
|
|
443
|
+
|
|
444
|
+
result = dict(schema)
|
|
445
|
+
|
|
446
|
+
# Remove additionalProperties (not supported by Gemini)
|
|
447
|
+
if "additionalProperties" in result:
|
|
448
|
+
del result["additionalProperties"]
|
|
449
|
+
|
|
450
|
+
# Ensure array types have items defined (Gemini requires this)
|
|
451
|
+
# Check both explicit type and type within anyOf/oneOf
|
|
452
|
+
is_array = result.get("type") == "array"
|
|
453
|
+
if not is_array and "type" in result and isinstance(result["type"], list):
|
|
454
|
+
is_array = "array" in result["type"]
|
|
455
|
+
|
|
456
|
+
if is_array and "items" not in result:
|
|
457
|
+
result["items"] = {"type": "string"} # Default to string array
|
|
458
|
+
|
|
459
|
+
# Recursively process nested schemas in properties
|
|
460
|
+
if "properties" in result and isinstance(result["properties"], dict):
|
|
461
|
+
for prop_name, prop_schema in result["properties"].items():
|
|
462
|
+
if isinstance(prop_schema, dict):
|
|
463
|
+
result["properties"][prop_name] = self._convert_schema_for_gemini(prop_schema)
|
|
464
|
+
|
|
465
|
+
# Process items in arrays
|
|
466
|
+
if "items" in result and isinstance(result["items"], dict):
|
|
467
|
+
result["items"] = self._convert_schema_for_gemini(result["items"])
|
|
468
|
+
|
|
469
|
+
# Process anyOf, oneOf, allOf schemas
|
|
470
|
+
for key in ("anyOf", "oneOf", "allOf"):
|
|
471
|
+
if key in result and isinstance(result[key], list):
|
|
472
|
+
result[key] = [
|
|
473
|
+
self._convert_schema_for_gemini(sub_schema)
|
|
474
|
+
for sub_schema in result[key]
|
|
475
|
+
if isinstance(sub_schema, dict)
|
|
476
|
+
]
|
|
477
|
+
# If any sub-schema is an array type, ensure it has items
|
|
478
|
+
for sub_schema in result[key]:
|
|
479
|
+
if isinstance(sub_schema, dict):
|
|
480
|
+
sub_is_array = sub_schema.get("type") == "array"
|
|
481
|
+
if sub_is_array and "items" not in sub_schema:
|
|
482
|
+
sub_schema["items"] = {"type": "string"}
|
|
483
|
+
|
|
484
|
+
# Process nested definitions/defs
|
|
485
|
+
for key in ("definitions", "$defs"):
|
|
486
|
+
if key in result and isinstance(result[key], dict):
|
|
487
|
+
result[key] = {
|
|
488
|
+
name: self._convert_schema_for_gemini(def_schema)
|
|
489
|
+
for name, def_schema in result[key].items()
|
|
490
|
+
if isinstance(def_schema, dict)
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
return result
|
|
494
|
+
|
|
495
|
+
def generate_batch(
|
|
496
|
+
self,
|
|
497
|
+
batch_messages: list[list[dict[str, str]]],
|
|
498
|
+
tools: list[ToolDefinition] | None = None,
|
|
499
|
+
) -> list[ModelResponse]:
|
|
500
|
+
"""Generate responses for a batch of message lists.
|
|
501
|
+
|
|
502
|
+
Uses asyncio.gather for parallel execution with rate limiting.
|
|
503
|
+
|
|
504
|
+
Args:
|
|
505
|
+
batch_messages: List of message lists
|
|
506
|
+
tools: Optional list of available tools
|
|
507
|
+
|
|
508
|
+
Returns:
|
|
509
|
+
List of ModelResponse objects
|
|
510
|
+
"""
|
|
511
|
+
return asyncio.run(self._generate_batch_async(batch_messages, tools))
|
|
512
|
+
|
|
513
|
+
async def _generate_batch_async(
|
|
514
|
+
self,
|
|
515
|
+
batch_messages: list[list[dict[str, str]]],
|
|
516
|
+
tools: list[ToolDefinition] | None = None,
|
|
517
|
+
) -> list[ModelResponse]:
|
|
518
|
+
"""Generate batch responses using asyncio.gather."""
|
|
519
|
+
tasks = [self.generate_async(messages, tools) for messages in batch_messages]
|
|
520
|
+
return list(await asyncio.gather(*tasks))
|
|
521
|
+
|
|
522
|
+
def cleanup(self) -> None:
|
|
523
|
+
"""Clean up resources.
|
|
524
|
+
|
|
525
|
+
Cloud clients don't typically need explicit cleanup.
|
|
526
|
+
"""
|
|
527
|
+
pass
|