wuwei 3.2.1__tar.gz → 3.2.2__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.
- {wuwei-3.2.1 → wuwei-3.2.2}/CHANGELOG.md +7 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/PKG-INFO +2 -2
- {wuwei-3.2.1 → wuwei-3.2.2}/USAGE.md +29 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/docs/RELEASING.md +4 -4
- {wuwei-3.2.1 → wuwei-3.2.2}/docs/changelog.md +7 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/pyproject.toml +2 -2
- wuwei-3.2.2/tests/test_openai_sdk_capabilities.py +127 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/agent/agent.py +3 -4
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/llm/adapters/openai.py +4 -13
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/llm/adapters/responses.py +12 -14
- wuwei-3.2.2/wuwei/llm/adapters/usage.py +27 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/llm/gateway.py +1 -1
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/planning/executor.py +2 -2
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/runtime/agent_runner.py +4 -6
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei.egg-info/PKG-INFO +2 -2
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei.egg-info/SOURCES.txt +2 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei.egg-info/requires.txt +1 -1
- {wuwei-3.2.1 → wuwei-3.2.2}/LICENSE +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/MANIFEST.in +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/MIGRATION.md +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/README.md +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/docs/api/reference.md +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/docs/core/agent.md +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/docs/core/architecture.md +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/docs/core/session.md +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/docs/examples/minimal.md +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/docs/examples/offline.md +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/docs/examples/session.md +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/docs/getting-started/configuration.md +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/docs/getting-started/installation.md +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/docs/getting-started/quickstart.md +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/docs/governance.md +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/docs/hitl/overview.md +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/docs/index.md +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/docs/memory/compression.md +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/docs/memory/context-window.md +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/docs/memory/context.md +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/docs/memory/storage.md +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/docs/operations.md +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/docs/planning/overview.md +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/docs/planning/task-dag.md +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/docs/plugin.md +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/docs/protocols.md +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/docs/skill/authoring.md +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/docs/skill/overview.md +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/docs/stylesheets/extra.css +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/docs/tools/custom.md +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/docs/tools/overview.md +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/examples/advanced_features.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/examples/decision_client_demo.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/examples/full_flow.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/examples/repl_agent.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/examples/skills/code-review/SKILL.md +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/examples/skills/code-review/scripts/count_lines.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/mkdocs.yml +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/setup.cfg +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/tests/conftest.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/tests/test_boundary_hardening.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/tests/test_builtin_tools.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/tests/test_core.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/tests/test_coverage_gaps.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/tests/test_delegation_hardening.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/tests/test_documentation_hardening.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/tests/test_e2e_all.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/tests/test_evals.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/tests/test_framework.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/tests/test_fs.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/tests/test_graph.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/tests/test_integration.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/tests/test_mcp.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/tests/test_middleware.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/tests/test_middleware_lifecycle.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/tests/test_model_hardening.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/tests/test_multi_agent_integration.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/tests/test_runtime_hardening.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/tests/test_sandbox.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/tests/test_serve.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/tests/test_skill_execution.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/tests/test_skill_integration.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/tests/test_v31_core.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/tests/test_v32_features.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/tests/test_v32_kernel.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/tests/test_vectorstore.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/__init__.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/agent/__init__.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/agent/async_sub_agent.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/agent/base.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/agent/multi_agent.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/agent/session.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/agent/sub_agent.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/codemode.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/config/__init__.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/config/yaml_loader.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/core/__init__.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/core/errors.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/core/message.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/core/runnable.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/core/types.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/debug.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/decision/__init__.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/decision/client.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/evals/__init__.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/evals/__main__.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/evals/case.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/evals/cli.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/evals/dataset.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/evals/report.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/evals/runner.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/fs/__init__.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/fs/base.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/fs/local.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/fs/memory.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/fs/tools.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/gateway/__init__.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/gateway/adapters/__init__.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/gateway/adapters/dingtalk.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/gateway/adapters/feishu.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/gateway/adapters/telegram.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/gateway/adapters/wechat.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/gateway/base.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/gateway/webhook.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/graph/__init__.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/graph/checkpoint.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/graph/graph.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/graph/reducers.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/graph/state.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/llm/__init__.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/llm/adapters/__init__.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/llm/adapters/anthropic.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/llm/adapters/base.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/llm/types.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/mcp/__init__.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/mcp/client.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/mcp/config.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/mcp/session.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/mcp/tool_search.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/mcp/tools.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/memory/__init__.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/memory/context.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/memory/context_compressor.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/memory/context_window.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/memory/curator.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/memory/embedder.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/memory/knowledge_store.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/memory/memory_store.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/memory/memory_types.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/memory/storage.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/middleware/__init__.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/middleware/base.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/middleware/budget.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/middleware/context_compression.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/middleware/hitl.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/middleware/ledger.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/middleware/logging.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/middleware/memory.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/middleware/rag.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/middleware/stack.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/middleware/storage.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/observability/__init__.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/observability/tracing.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/parsers/__init__.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/parsers/base.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/parsers/json.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/parsers/list.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/parsers/pydantic.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/planning/__init__.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/planning/planner.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/planning/task.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/plugin/__init__.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/plugin/builtin/__init__.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/plugin/builtin/calc.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/plugin/builtin/decision.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/plugin/builtin/file.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/plugin/builtin/git.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/plugin/builtin/http.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/plugin/builtin/json_tools.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/plugin/builtin/mcp.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/plugin/builtin/npm.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/plugin/builtin/python.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/plugin/builtin/rag.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/plugin/builtin/skill.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/plugin/builtin/text.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/plugin/builtin/time_plugin.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/plugin/builtin/vfs.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/plugin/manager.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/plugin/plugin.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/runtime/__init__.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/runtime/action_log.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/runtime/context.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/runtime/event_bus.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/runtime/interrupt.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/sandbox/__init__.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/sandbox/base.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/sandbox/docker.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/sandbox/e2b.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/sandbox/factory.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/sandbox/local.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/security/__init__.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/security/permissions.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/serve/__init__.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/serve/_http.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/serve/a2a.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/serve/ag_ui.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/serve/mcp_server.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/skill/__init__.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/skill/fs_provider.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/skill/skill.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/streaming/__init__.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/streaming/types.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/tools/__init__.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/tools/executor.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/tools/registry.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/tools/tool.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/vectorstore/__init__.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/vectorstore/_util.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/vectorstore/adapter.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/vectorstore/base.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/vectorstore/chroma.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/vectorstore/factory.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/vectorstore/inmemory.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/vectorstore/milvus.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/vectorstore/pgvector.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei/vectorstore/qdrant.py +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei.egg-info/dependency_links.txt +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei.egg-info/entry_points.txt +0 -0
- {wuwei-3.2.1 → wuwei-3.2.2}/wuwei.egg-info/top_level.txt +0 -0
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# 更新日志
|
|
2
2
|
|
|
3
|
+
## 3.2.2 — 2026-10-28
|
|
4
|
+
|
|
5
|
+
- OpenAI SDK 最低版本更新至 3.40.0,Responses 支持显式配置 prompt_cache_options / prompt_cache_key。
|
|
6
|
+
- 缓存读取、缓存写入和推理 token 细分统计贯通普通/流式运行、委派与规划汇总,不重复计入总 token。
|
|
7
|
+
- Responses 默认省略 temperature;保留显式配置、Chat Completions 默认值和适配器原有位置参数兼容性。
|
|
8
|
+
- 补充缓存配置、预算估算边界及离线 SDK 契约测试。
|
|
9
|
+
|
|
3
10
|
## 3.2.1 — 2026-10-28
|
|
4
11
|
|
|
5
12
|
- 更新补丁版本号,承接执行契约、模型接入及协议兼容性加固。
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: wuwei
|
|
3
|
-
Version: 3.2.
|
|
3
|
+
Version: 3.2.2
|
|
4
4
|
Summary: Wuwei - 无为而治的 AI 智能体框架
|
|
5
5
|
Author-email: xiaojiaenen <xiaojiaenen@163.com>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -10,7 +10,7 @@ Project-URL: Documentation, https://github.com/xiaojiaenen/wuwei
|
|
|
10
10
|
Requires-Python: >=3.11
|
|
11
11
|
Description-Content-Type: text/markdown
|
|
12
12
|
License-File: LICENSE
|
|
13
|
-
Requires-Dist: openai>=3.
|
|
13
|
+
Requires-Dist: openai>=3.40.0
|
|
14
14
|
Requires-Dist: pydantic>=2.0.0
|
|
15
15
|
Requires-Dist: pyyaml>=6.0.3
|
|
16
16
|
Requires-Dist: httpx>=0.25.0
|
|
@@ -27,6 +27,35 @@ llm = LLMGateway({"provider": "openai", "client": client,
|
|
|
27
27
|
|
|
28
28
|
框架自建 SDK client 默认关闭 SDK 重试,让网关负责分类重试。借入客户端也应明确 retry 所有权;否则 SDK 与框架尝试次数可能相乘。`set_fallback(other_gateway)` 借用完整 fallback 策略,调用方管理 other_gateway 生命周期。已发布流式内容、工具或 usage 元数据后不重放请求。确定性参数错误不做传输重试。
|
|
29
29
|
|
|
30
|
+
## OpenAI Responses:缓存与新模型参数
|
|
31
|
+
|
|
32
|
+
Responses 默认不发送 `temperature`,避免向不支持采样参数的模型强加旧默认值。显式配置数字仍会发送;`None` 表示省略,由服务端决定。Chat Completions 保留 `temperature=0.2` 兼容默认值;使用不支持该参数的模型时显式设为 `None`。
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
import os
|
|
36
|
+
from wuwei import LLMGateway
|
|
37
|
+
|
|
38
|
+
llm = LLMGateway({
|
|
39
|
+
"provider": "responses",
|
|
40
|
+
"api_key": os.environ["OPENAI_API_KEY"],
|
|
41
|
+
"model": os.environ["OPENAI_MODEL"],
|
|
42
|
+
"max_output_tokens": 4096,
|
|
43
|
+
"prompt_cache_key": "wuwei:assistant:v1",
|
|
44
|
+
"prompt_cache_options": {"mode": "implicit", "ttl": "30m"},
|
|
45
|
+
})
|
|
46
|
+
# 使用完 await llm.aclose()。
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
缓存参数要求支持该能力的 OpenAI 模型和服务端;自定义兼容端点未必接受。模型名称不设硬编码白名单,`reasoning` 等提供商参数可透传,但具体参数组合仍须符合模型契约。缓存 key 仅为稳定路由提示,不保证命中,也不要放入密钥或敏感身份信息。
|
|
50
|
+
|
|
51
|
+
上例采用隐式断点。`mode="explicit"` 需要额外的内容块断点;当前字符串消息入口不提供断点构建能力,不应只切换 mode 而期待缓存生效。请求级 TTL 与旧 `prompt_cache_retention` 含义不同,框架不会自动互相转换。
|
|
52
|
+
|
|
53
|
+
Chat Completions 推理模型可配置 `temperature=None`、`max_completion_tokens=4096`;此时不会同时发送旧 `max_tokens`。`reasoning_effort` 等参数由模型决定是否支持,不通过模型名称猜测或静默删除显式配置。
|
|
54
|
+
|
|
55
|
+
响应 `usage` 在服务端提供时包含 `cached_tokens`(缓存读取)、`cache_write_tokens`(缓存写入)、`reasoning_tokens`(推理输出);普通运行、流式运行、子任务和规划汇总保留这些字段。缺失字段不伪造为已测量值。这些是输入/输出 token 的细分,不额外加到 `total_tokens`。
|
|
56
|
+
|
|
57
|
+
预算仍按配置的输入/输出价格估算,不根据这些细分字段自动套用缓存折扣或写入附加费;需要精确账单时,以服务端账单为准。新模型应显式配置预算价格,避免使用通用估算价。
|
|
58
|
+
|
|
30
59
|
## 注册有参数契约的工具
|
|
31
60
|
|
|
32
61
|
```python
|
|
@@ -25,9 +25,9 @@
|
|
|
25
25
|
```bash
|
|
26
26
|
git switch master
|
|
27
27
|
git pull --ff-only origin master
|
|
28
|
-
# 当前项目版本为 3.2.
|
|
29
|
-
git tag -a v3.2.
|
|
30
|
-
git push origin v3.2.
|
|
28
|
+
# 当前项目版本为 3.2.2;后续按实际版本修改标签。
|
|
29
|
+
git tag -a v3.2.2 -m 'Release 3.2.2'
|
|
30
|
+
git push origin v3.2.2
|
|
31
31
|
```
|
|
32
32
|
|
|
33
33
|
在 Actions 查看 Publish to PyPI,必要时批准 `pypi` 环境部署。推送标签会触发正式上传;不要在绑定完成前推送。标签必须严格等于 `v` + `project.version`,否则发布会被阻止。
|
|
@@ -35,7 +35,7 @@ git push origin v3.2.1
|
|
|
35
35
|
上传后在 https://pypi.org/project/wuwei/ 确认版本,并在独立环境安装验证:
|
|
36
36
|
|
|
37
37
|
```bash
|
|
38
|
-
uv run --isolated --no-project --with 'wuwei==3.2.
|
|
38
|
+
uv run --isolated --no-project --with 'wuwei==3.2.2' \
|
|
39
39
|
python -c "from importlib.metadata import version; print(version('wuwei'))"
|
|
40
40
|
```
|
|
41
41
|
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# 更新日志
|
|
2
2
|
|
|
3
|
+
## 3.2.2 — 2026-10-28
|
|
4
|
+
|
|
5
|
+
- OpenAI SDK 最低版本更新至 3.40.0,Responses 支持显式配置 prompt_cache_options / prompt_cache_key。
|
|
6
|
+
- 缓存读取、缓存写入和推理 token 细分统计贯通普通/流式运行、委派与规划汇总,不重复计入总 token。
|
|
7
|
+
- Responses 默认省略 temperature;保留显式配置、Chat Completions 默认值和适配器原有位置参数兼容性。
|
|
8
|
+
- 补充缓存配置、预算估算边界及离线 SDK 契约测试。
|
|
9
|
+
|
|
3
10
|
## 3.2.1 — 2026-10-28
|
|
4
11
|
|
|
5
12
|
- 更新补丁版本号,承接执行契约、模型接入及协议兼容性加固。
|
|
@@ -4,14 +4,14 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "wuwei"
|
|
7
|
-
version = "3.2.
|
|
7
|
+
version = "3.2.2"
|
|
8
8
|
description = "Wuwei - 无为而治的 AI 智能体框架"
|
|
9
9
|
authors = [{name = "xiaojiaenen", email = "xiaojiaenen@163.com"}]
|
|
10
10
|
license = "Apache-2.0"
|
|
11
11
|
readme = "README.md"
|
|
12
12
|
requires-python = ">=3.11"
|
|
13
13
|
dependencies = [
|
|
14
|
-
"openai>=3.
|
|
14
|
+
"openai>=3.40.0",
|
|
15
15
|
"pydantic>=2.0.0",
|
|
16
16
|
"pyyaml>=6.0.3",
|
|
17
17
|
"httpx>=0.25.0",
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
from types import SimpleNamespace as Ns
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from wuwei.llm.adapters.openai import OpenAIAdapter
|
|
6
|
+
from wuwei.llm.adapters.responses import ResponsesAdapter
|
|
7
|
+
from wuwei.llm.gateway import LLMGateway
|
|
8
|
+
from wuwei.runtime.agent_runner import _merge_usage
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_modern_responses_omits_sampling_unless_explicit():
|
|
12
|
+
adapter = LLMGateway({'provider': 'responses', 'api_key': 'unused', 'client': Ns()}).adapter
|
|
13
|
+
assert 'temperature' not in adapter.build_request([])
|
|
14
|
+
assert adapter.build_request([], temperature=0.7)['temperature'] == 0.7
|
|
15
|
+
assert 'temperature' not in adapter.build_request([], temperature=None)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_legacy_chat_defaults_and_explicit_modern_parameters():
|
|
19
|
+
old = OpenAIAdapter('unused', client=Ns())
|
|
20
|
+
assert old.build_request([])['temperature'] == 0.2
|
|
21
|
+
modern = LLMGateway({'api_key': 'unused', 'client': Ns(), 'temperature': None,
|
|
22
|
+
'max_completion_tokens': 100, 'reasoning_effort': 'high'})
|
|
23
|
+
request = modern.adapter.build_request([])
|
|
24
|
+
assert 'temperature' not in request and 'max_tokens' not in request
|
|
25
|
+
assert request['max_completion_tokens'] == 100
|
|
26
|
+
assert request['reasoning_effort'] == 'high'
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@pytest.mark.parametrize('stream', [False, True])
|
|
30
|
+
def test_cache_parameters_reach_sdk(stream):
|
|
31
|
+
adapter = ResponsesAdapter('unused', client=Ns(),
|
|
32
|
+
prompt_cache_options={'mode': 'explicit', 'ttl': '30m'},
|
|
33
|
+
prompt_cache_key='stable-prefix')
|
|
34
|
+
request = adapter.build_request([], stream=stream)
|
|
35
|
+
assert request['prompt_cache_options'] == {'mode': 'explicit', 'ttl': '30m'}
|
|
36
|
+
assert request['prompt_cache_key'] == 'stable-prefix'
|
|
37
|
+
assert request['stream'] == stream
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def response_usage():
|
|
41
|
+
from openai.types.responses.response_usage import ResponseUsage
|
|
42
|
+
return ResponseUsage(input_tokens=100, output_tokens=30, total_tokens=130,
|
|
43
|
+
input_tokens_details={'cached_tokens': 60, 'cache_write_tokens': 20},
|
|
44
|
+
output_tokens_details={'reasoning_tokens': 10})
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_responses_usage_details_nonstream_and_stream():
|
|
48
|
+
adapter = ResponsesAdapter('unused', client=Ns())
|
|
49
|
+
raw = Ns(output=[], usage=response_usage(), status='completed', model='test')
|
|
50
|
+
usage = adapter.parse_response(raw).usage
|
|
51
|
+
assert usage == {'prompt_tokens': 100, 'completion_tokens': 30, 'total_tokens': 130,
|
|
52
|
+
'cached_tokens': 60, 'cache_write_tokens': 20, 'reasoning_tokens': 10}
|
|
53
|
+
event = adapter.parse_stream_chunk(Ns(type='response.completed', response=raw))
|
|
54
|
+
assert event['usage'] == usage
|
|
55
|
+
total = {'prompt_tokens': 0, 'completion_tokens': 0, 'total_tokens': 0}
|
|
56
|
+
_merge_usage(total, usage)
|
|
57
|
+
_merge_usage(total, usage)
|
|
58
|
+
assert total['cached_tokens'] == 120 and total['total_tokens'] == 260
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def test_chat_usage_details_and_missing_fields():
|
|
62
|
+
usage = Ns(prompt_tokens=100, completion_tokens=30, total_tokens=130,
|
|
63
|
+
prompt_tokens_details=Ns(cached_tokens=60),
|
|
64
|
+
completion_tokens_details=Ns(reasoning_tokens=10))
|
|
65
|
+
parsed = OpenAIAdapter._parse_usage(usage)
|
|
66
|
+
assert parsed['cached_tokens'] == 60 and parsed['reasoning_tokens'] == 10
|
|
67
|
+
minimal = OpenAIAdapter._parse_usage(Ns(prompt_tokens=1, completion_tokens=2))
|
|
68
|
+
assert minimal == {'prompt_tokens': 1, 'completion_tokens': 2, 'total_tokens': 3}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@pytest.mark.asyncio
|
|
72
|
+
@pytest.mark.parametrize('stream', [False, True])
|
|
73
|
+
async def test_details_survive_agent_and_session(stream):
|
|
74
|
+
from tests.test_runtime_hardening import make_agent, response
|
|
75
|
+
answer = response('done')
|
|
76
|
+
answer.usage = ResponsesAdapter._parse_usage(response_usage())
|
|
77
|
+
agent = make_agent([answer])
|
|
78
|
+
session = agent.create_session()
|
|
79
|
+
if stream:
|
|
80
|
+
events = [event async for event in agent.stream_events('hello', session=session)]
|
|
81
|
+
usage = events[-1].data['usage']
|
|
82
|
+
else:
|
|
83
|
+
usage = (await agent.run('hello', session=session)).usage
|
|
84
|
+
assert usage['cached_tokens'] == 60 and usage['reasoning_tokens'] == 10
|
|
85
|
+
assert usage['total_tokens'] == 130
|
|
86
|
+
assert session.last_usage == usage
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def test_plan_aggregation_preserves_details():
|
|
90
|
+
from wuwei.planning import PlanExecutor
|
|
91
|
+
executor = PlanExecutor(Ns())
|
|
92
|
+
detail = ResponsesAdapter._parse_usage(response_usage())
|
|
93
|
+
executor._tasks_usage = [(detail, 1), (detail, 1)]
|
|
94
|
+
usage, calls = executor._aggregate_usage()
|
|
95
|
+
assert calls == 2 and usage['cache_write_tokens'] == 40
|
|
96
|
+
assert usage['total_tokens'] == 260
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@pytest.mark.asyncio
|
|
100
|
+
async def test_cache_request_serialized_by_actual_sdk():
|
|
101
|
+
import json
|
|
102
|
+
import httpx
|
|
103
|
+
from openai import AsyncOpenAI
|
|
104
|
+
|
|
105
|
+
seen = []
|
|
106
|
+
def handler(request):
|
|
107
|
+
seen.append(json.loads(request.content))
|
|
108
|
+
return httpx.Response(200, json={'id': 'resp_1', 'object': 'response',
|
|
109
|
+
'created_at': 0, 'status': 'completed', 'model': 'test',
|
|
110
|
+
'output': [], 'usage': response_usage().model_dump()})
|
|
111
|
+
|
|
112
|
+
async with AsyncOpenAI(api_key='unused', max_retries=0,
|
|
113
|
+
http_client=httpx.AsyncClient(transport=httpx.MockTransport(handler))) as client:
|
|
114
|
+
adapter = ResponsesAdapter('unused', client=client,
|
|
115
|
+
prompt_cache_options={'mode': 'implicit', 'ttl': '30m'})
|
|
116
|
+
raw = await adapter.call(adapter.build_request([]))
|
|
117
|
+
assert adapter.parse_response(raw).usage['cached_tokens'] == 60
|
|
118
|
+
assert seen[0]['prompt_cache_options']['ttl'] == '30m'
|
|
119
|
+
assert 'temperature' not in seen[0]
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def test_responses_original_positional_constructor():
|
|
123
|
+
client = Ns()
|
|
124
|
+
adapter = ResponsesAdapter('unused', 'test', None, 0.4, 42, client)
|
|
125
|
+
request = adapter.build_request([])
|
|
126
|
+
assert request['max_output_tokens'] == 42 and request['temperature'] == 0.4
|
|
127
|
+
assert adapter.client is client
|
|
@@ -333,10 +333,9 @@ class Agent(BaseSessionAgent):
|
|
|
333
333
|
before = metadata["llm_calls"]
|
|
334
334
|
tasks = await p.plan_task(goal)
|
|
335
335
|
if before == metadata["llm_calls"]:
|
|
336
|
-
for key in
|
|
337
|
-
metadata["usage_total"]
|
|
338
|
-
|
|
339
|
-
).get(key, 0)
|
|
336
|
+
for key, value in (getattr(p, "last_usage", {}) or {}).items():
|
|
337
|
+
totals = metadata["usage_total"]
|
|
338
|
+
totals[key] = totals.get(key, 0) + (value or 0)
|
|
340
339
|
metadata["llm_calls"] += getattr(p, "last_llm_calls", 0)
|
|
341
340
|
for task in tasks:
|
|
342
341
|
task.status = "pending"
|
|
@@ -19,6 +19,7 @@ from ..types import (
|
|
|
19
19
|
normalize_finish_reason,
|
|
20
20
|
)
|
|
21
21
|
from .base import BaseAdapter, make_client
|
|
22
|
+
from .usage import parse_openai_usage
|
|
22
23
|
|
|
23
24
|
|
|
24
25
|
class OpenAIAdapter(BaseAdapter):
|
|
@@ -31,7 +32,7 @@ class OpenAIAdapter(BaseAdapter):
|
|
|
31
32
|
api_key: str,
|
|
32
33
|
model: str = "gpt-4o",
|
|
33
34
|
base_url: str | None = None,
|
|
34
|
-
temperature: float = 0.2,
|
|
35
|
+
temperature: float | None = 0.2,
|
|
35
36
|
max_tokens: int = 4096,
|
|
36
37
|
client=None,
|
|
37
38
|
client_factory=None,
|
|
@@ -82,7 +83,7 @@ class OpenAIAdapter(BaseAdapter):
|
|
|
82
83
|
request["tools"] = [
|
|
83
84
|
tool.to_schema() if hasattr(tool, "to_schema") else tool for tool in tools
|
|
84
85
|
]
|
|
85
|
-
return request
|
|
86
|
+
return {k: v for k, v in request.items() if v is not None}
|
|
86
87
|
|
|
87
88
|
async def call(self, request: dict[str, Any]) -> Any:
|
|
88
89
|
return await self.client.chat.completions.create(**request)
|
|
@@ -166,17 +167,7 @@ class OpenAIAdapter(BaseAdapter):
|
|
|
166
167
|
@staticmethod
|
|
167
168
|
def _parse_usage(usage: Any) -> dict[str, int]:
|
|
168
169
|
"""统一解析 usage,避免不同端点返回 None 或缺字段时崩溃。"""
|
|
169
|
-
|
|
170
|
-
return {"prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0}
|
|
171
|
-
prompt = getattr(usage, "prompt_tokens", 0) or 0
|
|
172
|
-
completion = getattr(usage, "completion_tokens", 0) or 0
|
|
173
|
-
total = getattr(usage, "total_tokens", None)
|
|
174
|
-
total = total if total is not None else prompt + completion
|
|
175
|
-
return {
|
|
176
|
-
"prompt_tokens": prompt,
|
|
177
|
-
"completion_tokens": completion,
|
|
178
|
-
"total_tokens": total,
|
|
179
|
-
}
|
|
170
|
+
return parse_openai_usage(usage)
|
|
180
171
|
|
|
181
172
|
@staticmethod
|
|
182
173
|
def _get_extra_field(obj: Any, field_name: str) -> Any:
|
|
@@ -18,6 +18,7 @@ from ..types import (
|
|
|
18
18
|
ToolCall,
|
|
19
19
|
)
|
|
20
20
|
from .base import BaseAdapter, make_client
|
|
21
|
+
from .usage import parse_openai_usage
|
|
21
22
|
|
|
22
23
|
|
|
23
24
|
class ResponsesAdapter(BaseAdapter):
|
|
@@ -30,11 +31,14 @@ class ResponsesAdapter(BaseAdapter):
|
|
|
30
31
|
api_key: str,
|
|
31
32
|
model: str = "gpt-4o",
|
|
32
33
|
base_url: str | None = None,
|
|
33
|
-
temperature: float =
|
|
34
|
+
temperature: float | None = None,
|
|
34
35
|
max_output_tokens: int = 4096,
|
|
35
36
|
client=None,
|
|
36
37
|
client_factory=None,
|
|
37
38
|
owns_client=None,
|
|
39
|
+
*,
|
|
40
|
+
prompt_cache_options: dict[str, Any] | None = None,
|
|
41
|
+
prompt_cache_key: str | None = None,
|
|
38
42
|
**kwargs,
|
|
39
43
|
):
|
|
40
44
|
self.client, self.owns_client = make_client(
|
|
@@ -43,6 +47,10 @@ class ResponsesAdapter(BaseAdapter):
|
|
|
43
47
|
max_output_tokens = kwargs.pop("max_tokens", max_output_tokens)
|
|
44
48
|
self.model = model
|
|
45
49
|
self.default_params: dict[str, Any] = {k: v for k, v in kwargs.items() if v is not None}
|
|
50
|
+
if prompt_cache_options is not None:
|
|
51
|
+
self.default_params["prompt_cache_options"] = dict(prompt_cache_options)
|
|
52
|
+
if prompt_cache_key is not None:
|
|
53
|
+
self.default_params["prompt_cache_key"] = prompt_cache_key
|
|
46
54
|
self.max_output_tokens = max_output_tokens
|
|
47
55
|
self.temperature = temperature
|
|
48
56
|
|
|
@@ -125,7 +133,7 @@ class ResponsesAdapter(BaseAdapter):
|
|
|
125
133
|
request["instructions"] = instructions
|
|
126
134
|
if tools:
|
|
127
135
|
request["tools"] = self._convert_tools(tools)
|
|
128
|
-
return request
|
|
136
|
+
return {k: v for k, v in request.items() if v is not None}
|
|
129
137
|
|
|
130
138
|
async def call(self, request: dict[str, Any]) -> Any:
|
|
131
139
|
return await self.client.responses.create(**request)
|
|
@@ -240,7 +248,7 @@ class ResponsesAdapter(BaseAdapter):
|
|
|
240
248
|
]
|
|
241
249
|
}
|
|
242
250
|
usage = self._parse_usage(getattr(response, "usage", None))
|
|
243
|
-
if
|
|
251
|
+
if getattr(response, "usage", None) is not None:
|
|
244
252
|
result["usage"] = usage
|
|
245
253
|
return result
|
|
246
254
|
|
|
@@ -250,14 +258,4 @@ class ResponsesAdapter(BaseAdapter):
|
|
|
250
258
|
|
|
251
259
|
@staticmethod
|
|
252
260
|
def _parse_usage(usage: Any) -> dict[str, int]:
|
|
253
|
-
|
|
254
|
-
return {"prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0}
|
|
255
|
-
prompt = getattr(usage, "input_tokens", 0) or 0
|
|
256
|
-
completion = getattr(usage, "output_tokens", 0) or 0
|
|
257
|
-
total = getattr(usage, "total_tokens", None)
|
|
258
|
-
total = total if total is not None else prompt + completion
|
|
259
|
-
return {
|
|
260
|
-
"prompt_tokens": prompt,
|
|
261
|
-
"completion_tokens": completion,
|
|
262
|
-
"total_tokens": total,
|
|
263
|
-
}
|
|
261
|
+
return parse_openai_usage(usage, responses=True)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Normalize OpenAI usage without counting detail tokens a second time."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def parse_openai_usage(usage, *, responses=False):
|
|
5
|
+
def field(obj, name, default=None):
|
|
6
|
+
return obj.get(name, default) if isinstance(obj, dict) else getattr(obj, name, default)
|
|
7
|
+
|
|
8
|
+
input_name, output_name = (
|
|
9
|
+
("input_tokens", "output_tokens") if responses else ("prompt_tokens", "completion_tokens")
|
|
10
|
+
)
|
|
11
|
+
prompt = field(usage, input_name, 0) or 0
|
|
12
|
+
completion = field(usage, output_name, 0) or 0
|
|
13
|
+
total = field(usage, "total_tokens")
|
|
14
|
+
result = {
|
|
15
|
+
"prompt_tokens": prompt,
|
|
16
|
+
"completion_tokens": completion,
|
|
17
|
+
"total_tokens": total if total is not None else prompt + completion,
|
|
18
|
+
}
|
|
19
|
+
for detail, names in (
|
|
20
|
+
(field(usage, f"{input_name}_details"), ("cached_tokens", "cache_write_tokens")),
|
|
21
|
+
(field(usage, f"{output_name}_details"), ("reasoning_tokens",)),
|
|
22
|
+
):
|
|
23
|
+
for name in names:
|
|
24
|
+
value = field(detail, name)
|
|
25
|
+
if value is not None:
|
|
26
|
+
result[name] = value
|
|
27
|
+
return result
|
|
@@ -110,7 +110,7 @@ class LLMGateway:
|
|
|
110
110
|
adapter_kwargs: dict[str, Any] = {
|
|
111
111
|
"api_key": config.get("api_key") or "missing",
|
|
112
112
|
"model": config.get("model", spec["default_model"]),
|
|
113
|
-
"temperature": config.get("temperature", 0.2),
|
|
113
|
+
"temperature": config.get("temperature", None if provider == "responses" else 0.2),
|
|
114
114
|
}
|
|
115
115
|
if provider == "responses":
|
|
116
116
|
adapter_kwargs["max_output_tokens"] = config.get(
|
|
@@ -310,7 +310,7 @@ class PlanExecutor:
|
|
|
310
310
|
usage = {"prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0}
|
|
311
311
|
llm_calls = 0
|
|
312
312
|
for u, calls in self._tasks_usage:
|
|
313
|
-
for k in
|
|
314
|
-
usage[k]
|
|
313
|
+
for k, value in u.items():
|
|
314
|
+
usage[k] = usage.get(k, 0) + (value or 0)
|
|
315
315
|
llm_calls += calls
|
|
316
316
|
return usage, llm_calls
|
|
@@ -28,8 +28,8 @@ def _empty_usage():
|
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
def _merge_usage(total, usage):
|
|
31
|
-
for key in
|
|
32
|
-
total[key]
|
|
31
|
+
for key, value in (usage or {}).items():
|
|
32
|
+
total[key] = total.get(key, 0) + (value or 0)
|
|
33
33
|
|
|
34
34
|
|
|
35
35
|
class AgentRunner:
|
|
@@ -373,10 +373,8 @@ class AgentRunner:
|
|
|
373
373
|
pending = chunk.tool_calls_complete
|
|
374
374
|
if chunk.finish_reason:
|
|
375
375
|
finish = chunk.finish_reason
|
|
376
|
-
for key in
|
|
377
|
-
call_usage[key] = max(
|
|
378
|
-
call_usage[key], (chunk.usage or {}).get(key, 0) or 0
|
|
379
|
-
)
|
|
376
|
+
for key, value in (chunk.usage or {}).items():
|
|
377
|
+
call_usage[key] = max(call_usage.get(key, 0), value or 0)
|
|
380
378
|
finally:
|
|
381
379
|
close = getattr(result, "aclose", None)
|
|
382
380
|
if close:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: wuwei
|
|
3
|
-
Version: 3.2.
|
|
3
|
+
Version: 3.2.2
|
|
4
4
|
Summary: Wuwei - 无为而治的 AI 智能体框架
|
|
5
5
|
Author-email: xiaojiaenen <xiaojiaenen@163.com>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -10,7 +10,7 @@ Project-URL: Documentation, https://github.com/xiaojiaenen/wuwei
|
|
|
10
10
|
Requires-Python: >=3.11
|
|
11
11
|
Description-Content-Type: text/markdown
|
|
12
12
|
License-File: LICENSE
|
|
13
|
-
Requires-Dist: openai>=3.
|
|
13
|
+
Requires-Dist: openai>=3.40.0
|
|
14
14
|
Requires-Dist: pydantic>=2.0.0
|
|
15
15
|
Requires-Dist: pyyaml>=6.0.3
|
|
16
16
|
Requires-Dist: httpx>=0.25.0
|
|
@@ -59,6 +59,7 @@ tests/test_middleware.py
|
|
|
59
59
|
tests/test_middleware_lifecycle.py
|
|
60
60
|
tests/test_model_hardening.py
|
|
61
61
|
tests/test_multi_agent_integration.py
|
|
62
|
+
tests/test_openai_sdk_capabilities.py
|
|
62
63
|
tests/test_runtime_hardening.py
|
|
63
64
|
tests/test_sandbox.py
|
|
64
65
|
tests/test_serve.py
|
|
@@ -126,6 +127,7 @@ wuwei/llm/adapters/anthropic.py
|
|
|
126
127
|
wuwei/llm/adapters/base.py
|
|
127
128
|
wuwei/llm/adapters/openai.py
|
|
128
129
|
wuwei/llm/adapters/responses.py
|
|
130
|
+
wuwei/llm/adapters/usage.py
|
|
129
131
|
wuwei/mcp/__init__.py
|
|
130
132
|
wuwei/mcp/client.py
|
|
131
133
|
wuwei/mcp/config.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|