dialectical-framework 2.0.5__tar.gz → 2.0.7__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.
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/PKG-INFO +1 -1
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/pyproject.toml +1 -1
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/agents/advisor/advisor.py +59 -27
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/agents/conversation_facilitator.py +32 -1
- dialectical_framework-2.0.7/src/dialectical_framework/exceptions/provider_errors.py +42 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/utils/bedrock_provider.py +144 -34
- dialectical_framework-2.0.7/src/dialectical_framework/utils/format_compat.py +136 -0
- dialectical_framework-2.0.7/src/dialectical_framework/utils/thinking_compat.py +401 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/utils/use_brain.py +17 -0
- dialectical_framework-2.0.5/src/dialectical_framework/utils/thinking_compat.py +0 -203
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/LICENSE +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/PATENTS +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/README.md +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/__init__.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/agents/__init__.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/agents/advisor/__init__.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/agents/advisor/build_policy.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/agents/advisor/migration.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/agents/advisor/reply_hygiene.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/agents/advisor/system_prompts.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/agents/advisor/tools/__init__.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/agents/advisor/tools/anchor.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/agents/advisor/tools/deepen.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/agents/advisor/tools/explore.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/agents/advisor/tools/ingest.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/agents/advisor/tools/note.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/agents/advisor/tools/record_decision.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/agents/advisor/tools/scoped.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/agents/advisor/tools/sync.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/agents/agent_context.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/agents/analyst/__init__.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/agents/analyst/analyst.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/agents/analyst/skills/__init__.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/agents/analyst/skills/anchor_theses.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/agents/analyst/skills/edit_perspective.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/agents/analyst/skills/expand_polarities.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/agents/analyst/skills/find_polarities.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/agents/analyst/skills/introduce_polarity.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/agents/analyst/skills/sketch_tetrad.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/agents/analyst/skills/surface_theses.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/agents/analyst/system_prompts.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/agents/analyst/tools/__init__.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/agents/analyst/tools/place_statement.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/agents/app_spec.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/agents/apps.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/agents/consultant/__init__.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/agents/consultant/consultant.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/agents/execution_report.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/agents/explorer/__init__.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/agents/explorer/explorer.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/agents/explorer/skills/__init__.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/agents/explorer/skills/build_wheels.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/agents/explorer/skills/explore_transformations.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/agents/explorer/skills/generate_synthesis.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/agents/explorer/system_prompts.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/agents/explorer/tools/__init__.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/agents/explorer/tools/create_nexus.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/agents/explorer/tools/expand_nexus.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/agents/explorer/tools/generate_synthesis.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/agents/explorer/tools/present_exploration.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/agents/orchestrator/__init__.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/agents/orchestrator/tools/__init__.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/agents/orchestrator/tools/add_input.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/agents/orchestrator/tools/audit_feasibility.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/agents/orchestrator/tools/create_dx_input.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/agents/orchestrator/tools/digest_input.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/agents/orchestrator/tools/discard.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/agents/orchestrator/tools/get_schema.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/agents/orchestrator/tools/inspect_node.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/agents/orchestrator/tools/present_analysis.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/agents/orchestrator/tools/query_graph.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/agents/orchestrator/tools/read_digest.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/agents/orchestrator/tools/read_input.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/agents/reasonable_concern.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/agents/stream_events.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/agents/toolsets.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/agents/turn_timing.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/concerns/__init__.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/concerns/ac_re_taxonomy.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/concerns/action_extraction.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/concerns/add_input.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/concerns/ai_dto/__init__.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/concerns/ai_dto/statement_dto.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/concerns/ai_dto/statements_deck_dto.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/concerns/antithesis_classification.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/concerns/antithesis_extraction.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/concerns/antithetical_thesis_detection.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/concerns/aspect_classification.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/concerns/aspect_generation.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/concerns/build_status.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/concerns/causality/__init__.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/concerns/causality/causality_estimator.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/concerns/causality/causality_estimator_balanced.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/concerns/causality/causality_estimator_criteria.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/concerns/causality/causality_estimator_desirable.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/concerns/causality/causality_estimator_feasible.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/concerns/causality/causality_estimator_realistic.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/concerns/causality/causality_normalizer.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/concerns/causality/estimator_resolver.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/concerns/causality_estimation.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/concerns/control_statements_check.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/concerns/create_dx_input.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/concerns/create_nexus.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/concerns/decision_coherence_check.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/concerns/decision_confirmation_check.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/concerns/diagonal_oppositions_check.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/concerns/dialectical_context.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/concerns/discard.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/concerns/display_text_edit.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/concerns/expand_nexus.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/concerns/perspective_combination.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/concerns/perspective_validation.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/concerns/positive_ac_re_apex_derivation.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/concerns/record_decision.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/concerns/scoring_scales.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/concerns/source_digest.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/concerns/statement_classification.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/concerns/statement_deduplication.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/concerns/statement_headline.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/concerns/statement_placement.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/concerns/synthesis_generation.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/concerns/tetrad_candidates.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/concerns/tetrad_grounding.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/concerns/tetrad_sketch.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/concerns/thesis_extraction.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/concerns/transformation_audit.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/concerns/transformation_generation.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/concerns/transformation_sketch.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/concerns/view_sketch.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/dialectical_reasoning.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/enums/__init__.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/enums/causality_preset.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/enums/di.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/events/__init__.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/events/graph_event.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/events/graph_event_bus.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/events/progress_event.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/exceptions/__init__.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/exceptions/node_errors.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/exceptions/resolver_errors.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/__init__.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/composite_input_resolver.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/dialexity_input_resolver.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/estimation_manager.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/mixins/__init__.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/mixins/incremental_build_mixin.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/mixins/intent_mixin.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/mixins/persistable_mixin.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/nodes/__init__.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/nodes/assessable_entity.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/nodes/base_node.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/nodes/case.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/nodes/cycle.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/nodes/decision.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/nodes/estimation.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/nodes/ideas.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/nodes/input.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/nodes/nexus.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/nodes/note.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/nodes/perspective.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/nodes/polarity.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/nodes/rationale.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/nodes/statement.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/nodes/synthesis.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/nodes/transformation.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/nodes/transition.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/nodes/wheel.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/relationship_manager.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/relationships/__init__.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/relationships/action_reflection_relationship.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/relationships/belongs_to_cycle_relationship.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/relationships/belongs_to_nexus_relationship.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/relationships/changed_to_relationship.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/relationships/contradiction_of_relationship.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/relationships/critiques_relationship.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/relationships/distilled_to_relationship.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/relationships/estimates_relationship.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/relationships/explains_relationship.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/relationships/grounded_in_relationship.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/relationships/has_input_relationship.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/relationships/has_statement_relationship.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/relationships/has_wheel_relationship.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/relationships/immutable_structure.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/relationships/is_source_of_relationship.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/relationships/is_target_of_relationship.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/relationships/negative_side_of_relationship.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/relationships/opposite_direction_relationship.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/relationships/opposite_of_relationship.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/relationships/polarity_relationship.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/relationships/positive_side_of_relationship.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/relationships/provides_relationship.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/relationships/synthesis_of_relationship.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/rendering.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/repositories/__init__.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/repositories/case_repository.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/repositories/cycle_repository.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/repositories/decision_repository.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/repositories/input_repository.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/repositories/nexus_repository.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/repositories/node_repository.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/repositories/note_repository.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/repositories/perspective_repository.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/repositories/polarity_repository.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/repositories/schema_repository.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/repositories/statement_repository.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/repositories/transformation_repository.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/repositories/wheel_repository.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/scope_context.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/verbatim_input_resolver.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/views.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/wheel_segment.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/graph/wheel_segment_polar_pair.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/protocols/__init__.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/protocols/has_config.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/protocols/input_resolver.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/py.typed +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/settings.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/settings_context.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/utils/__init__.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/utils/async_drain.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/utils/call_census.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/utils/chunking.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/utils/concurrency.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/utils/dc_replace.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/utils/decompose_probability_uniformly.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/utils/edge_context.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/utils/effect_logger.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/utils/input_context.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/utils/order_transitions.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/utils/progress.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/utils/retry_accounting.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/utils/sequence_generation.py +0 -0
- {dialectical_framework-2.0.5 → dialectical_framework-2.0.7}/src/dialectical_framework/utils/utterance.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: dialectical-framework
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.7
|
|
4
4
|
Summary: A dialectical framework for augmented intelligence. AI reasoning powered with dialectics supports humans in: system optimization (psychology, engineering, business, politics, etc.); dispute resolution (mediation, conflicts, negotiations, etc.); decision-making (dilemmas, challenging situations, win-win, etc.).
|
|
5
5
|
License: MIT
|
|
6
6
|
Keywords: dialectics,dialectical-reasoning,synthesis,thesis-antithesis,ai,artificial-intelligence,llm,reasoning-framework,philosophy,logic,argumentation,conflict-resolution,decision-making,critical-thinking,semantic-graph,mirascope,pydantic,perspectives,polarity-reasoning
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "dialectical-framework"
|
|
3
|
-
version = "2.0.
|
|
3
|
+
version = "2.0.7"
|
|
4
4
|
description = "A dialectical framework for augmented intelligence. AI reasoning powered with dialectics supports humans in: system optimization (psychology, engineering, business, politics, etc.); dispute resolution (mediation, conflicts, negotiations, etc.); decision-making (dilemmas, challenging situations, win-win, etc.)."
|
|
5
5
|
authors = ["Evaldas Taroza <evaldas@dialexity.com>"]
|
|
6
6
|
readme = "README.md"
|
|
@@ -42,6 +42,7 @@ from dialectical_framework.agents.stream_events import (ResponseComplete,
|
|
|
42
42
|
StreamEvent, TextDelta,
|
|
43
43
|
ToolResult, ToolStart)
|
|
44
44
|
from dialectical_framework.agents.toolsets import merge_app_tools
|
|
45
|
+
from dialectical_framework.exceptions.provider_errors import ModelRefusal
|
|
45
46
|
from dialectical_framework.agents.turn_timing import (ClosingOutcome,
|
|
46
47
|
DeferralOutcome,
|
|
47
48
|
TurnTiming)
|
|
@@ -850,7 +851,13 @@ class Advisor(SettingsAware):
|
|
|
850
851
|
# Around the provider round ONLY — the closing seam and the
|
|
851
852
|
# off-turn scheduling below run outside it on purpose.
|
|
852
853
|
with speaking(user_message):
|
|
853
|
-
|
|
854
|
+
try:
|
|
855
|
+
result = await self._conversation.submit(
|
|
856
|
+
ChatResponse, user_message
|
|
857
|
+
)
|
|
858
|
+
except ModelRefusal:
|
|
859
|
+
await self._settle_refused_turn(user_message)
|
|
860
|
+
raise
|
|
854
861
|
reply_path_s = (
|
|
855
862
|
deferred_wait_s
|
|
856
863
|
+ context_render_s
|
|
@@ -966,33 +973,37 @@ class Advisor(SettingsAware):
|
|
|
966
973
|
# Entered here, before the stream is opened, because the tool runs
|
|
967
974
|
# inside the generator while it is being iterated.
|
|
968
975
|
with speaking(user_message):
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
if hygiene is not None:
|
|
985
|
-
if isinstance(event, TextDelta):
|
|
986
|
-
clean = hygiene.feed(event.text)
|
|
987
|
-
if clean:
|
|
988
|
-
yield TextDelta(text=clean)
|
|
976
|
+
try:
|
|
977
|
+
async with aclosing(
|
|
978
|
+
self._conversation.submit_stream(ChatResponse, user_message)
|
|
979
|
+
) as rounds:
|
|
980
|
+
async for event in rounds:
|
|
981
|
+
if isinstance(event, ResponseComplete):
|
|
982
|
+
final_event = event
|
|
983
|
+
reply = event.message
|
|
984
|
+
# `continue`, so `submit_stream` is asked for one more
|
|
985
|
+
# event and runs to its own end instead of being closed
|
|
986
|
+
# while suspended at its yield. The seconds read below are
|
|
987
|
+
# safe either way — it stamps them as this event passes
|
|
988
|
+
# through it, not on exit — but running out is still the
|
|
989
|
+
# cleaner exit: the provider's connection is let go of by
|
|
990
|
+
# exhaustion rather than by a close unwinding a live frame.
|
|
989
991
|
continue
|
|
990
|
-
if
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
992
|
+
if hygiene is not None:
|
|
993
|
+
if isinstance(event, TextDelta):
|
|
994
|
+
clean = hygiene.feed(event.text)
|
|
995
|
+
if clean:
|
|
996
|
+
yield TextDelta(text=clean)
|
|
997
|
+
continue
|
|
998
|
+
if isinstance(event, (ToolStart, ToolResult)):
|
|
999
|
+
tail = hygiene.flush()
|
|
1000
|
+
if tail:
|
|
1001
|
+
yield TextDelta(text=tail)
|
|
1002
|
+
hygiene = HashCitationFilter()
|
|
1003
|
+
yield event
|
|
1004
|
+
except ModelRefusal:
|
|
1005
|
+
await self._settle_refused_turn(user_message)
|
|
1006
|
+
raise
|
|
996
1007
|
if hygiene is not None:
|
|
997
1008
|
tail = hygiene.flush()
|
|
998
1009
|
if tail:
|
|
@@ -2829,6 +2840,27 @@ class Advisor(SettingsAware):
|
|
|
2829
2840
|
logger.exception("Accepted-cost ground resolution failed (fail-soft)")
|
|
2830
2841
|
return None
|
|
2831
2842
|
|
|
2843
|
+
async def _settle_refused_turn(self, user_message: str) -> None:
|
|
2844
|
+
"""The closing work a refused turn still owes, before the refusal goes on.
|
|
2845
|
+
|
|
2846
|
+
A refusal can arrive AFTER tool rounds: the resume round is the one
|
|
2847
|
+
declined, so `record_decision` may already have written a Decision and
|
|
2848
|
+
`note` may already have kept a Note. The facilitator drops the turn
|
|
2849
|
+
from history, but the graph keeps what the tools wrote — and without
|
|
2850
|
+
this the recorded decision never gets its pathway weave (the seam's
|
|
2851
|
+
MODEL_RECORDED branch is what schedules it) and the noted tensions wait
|
|
2852
|
+
for the next turn to be offered. Only the work the tools already did is
|
|
2853
|
+
followed up: the classifier is not asked about a turn that was refused,
|
|
2854
|
+
so no decision is written here that the model did not write.
|
|
2855
|
+
Fail-soft: the refusal is what the host must see.
|
|
2856
|
+
"""
|
|
2857
|
+
try:
|
|
2858
|
+
if self._recorded_decision_this_turn():
|
|
2859
|
+
await self._repair_unrecorded_decision(user_message, "")
|
|
2860
|
+
self._schedule_noted_tensions()
|
|
2861
|
+
except Exception:
|
|
2862
|
+
logger.exception("Closing work after a refused turn failed (fail-soft)")
|
|
2863
|
+
|
|
2832
2864
|
def _recorded_decision_this_turn(self) -> bool:
|
|
2833
2865
|
"""Did `record_decision` already run, successfully, on this turn?
|
|
2834
2866
|
|
|
@@ -29,6 +29,7 @@ from dialectical_framework.agents.stream_events import (
|
|
|
29
29
|
ToolStart,
|
|
30
30
|
)
|
|
31
31
|
from dialectical_framework.agents.turn_timing import ToolRound
|
|
32
|
+
from dialectical_framework.exceptions.provider_errors import ModelRefusal
|
|
32
33
|
from dialectical_framework.protocols.has_config import SettingsAware
|
|
33
34
|
from dialectical_framework.utils.call_census import record_call
|
|
34
35
|
from dialectical_framework.utils.thinking_compat import conversational_round
|
|
@@ -37,7 +38,7 @@ from dialectical_framework.utils.retry_accounting import (RetryAccount,
|
|
|
37
38
|
from dialectical_framework.utils.use_brain import (prefill_token_kwargs,
|
|
38
39
|
retry_transient, use_brain)
|
|
39
40
|
|
|
40
|
-
from mirascope.llm import TextChunk, ThoughtChunk, ToolOutput
|
|
41
|
+
from mirascope.llm import FinishReason, TextChunk, ThoughtChunk, ToolOutput
|
|
41
42
|
|
|
42
43
|
if TYPE_CHECKING:
|
|
43
44
|
from mirascope.llm import UserContent
|
|
@@ -513,6 +514,7 @@ class ConversationFacilitator(SettingsAware):
|
|
|
513
514
|
Returns:
|
|
514
515
|
Structured response matching response_model
|
|
515
516
|
"""
|
|
517
|
+
history_before = len(self._messages)
|
|
516
518
|
self._messages.append(llm.messages.user(user_content))
|
|
517
519
|
self.last_tool_calls = []
|
|
518
520
|
self.last_tool_results = []
|
|
@@ -567,6 +569,10 @@ class ConversationFacilitator(SettingsAware):
|
|
|
567
569
|
# Mirascope's own request, so nothing in `use_brain` marks it).
|
|
568
570
|
with conversational_round():
|
|
569
571
|
response = await response.resume(tool_outputs)
|
|
572
|
+
# Mirascope's own request, below `use_brain`: a provider that
|
|
573
|
+
# does not raise on a refusal itself only says so here.
|
|
574
|
+
if getattr(response, "finish_reason", None) == FinishReason.REFUSAL:
|
|
575
|
+
raise ModelRefusal(getattr(response, "model_id", "unknown"))
|
|
570
576
|
|
|
571
577
|
# Sync full conversation history from the response chain
|
|
572
578
|
self._messages = list(response.messages)
|
|
@@ -580,6 +586,9 @@ class ConversationFacilitator(SettingsAware):
|
|
|
580
586
|
|
|
581
587
|
# Extract structured response
|
|
582
588
|
return await self._call_with_response_model(response_model)
|
|
589
|
+
except ModelRefusal:
|
|
590
|
+
self._forget_refused_turn(history_before)
|
|
591
|
+
raise
|
|
583
592
|
finally:
|
|
584
593
|
if epoch == self._turn_epoch:
|
|
585
594
|
self.last_submit_seconds = time.monotonic() - started
|
|
@@ -631,6 +640,7 @@ class ConversationFacilitator(SettingsAware):
|
|
|
631
640
|
#: with a later reading, or every ordinary turn would silently absorb
|
|
632
641
|
#: however long the consumer took to come back for the last event.
|
|
633
642
|
recorded = False
|
|
643
|
+
history_before = len(self._messages)
|
|
634
644
|
# Held in a name so it can be closed; see the docstring.
|
|
635
645
|
rounds = self._stream_turn(
|
|
636
646
|
response_model,
|
|
@@ -644,6 +654,9 @@ class ConversationFacilitator(SettingsAware):
|
|
|
644
654
|
self.last_submit_seconds = time.monotonic() - started
|
|
645
655
|
recorded = True
|
|
646
656
|
yield event
|
|
657
|
+
except ModelRefusal:
|
|
658
|
+
self._forget_refused_turn(history_before)
|
|
659
|
+
raise
|
|
647
660
|
finally:
|
|
648
661
|
# Before the close, which can raise: the figure is the point.
|
|
649
662
|
if not recorded and epoch == self._turn_epoch:
|
|
@@ -802,6 +815,13 @@ class ConversationFacilitator(SettingsAware):
|
|
|
802
815
|
first_chunk_at=first_chunk_at,
|
|
803
816
|
yielded_s=yielded_s,
|
|
804
817
|
)
|
|
818
|
+
if getattr(stream, "finish_reason", None) == FinishReason.REFUSAL:
|
|
819
|
+
# The awaited path raises at the provider; a stream only says so
|
|
820
|
+
# in its last chunk, after any text has been yielded. The host
|
|
821
|
+
# has shown a partial reply and should replace it — the
|
|
822
|
+
# exception is the signal. No `stop_details` on this path.
|
|
823
|
+
raise ModelRefusal(getattr(stream, "model_id", "unknown"))
|
|
824
|
+
|
|
805
825
|
if self.last_submit_first_delta_s is None and first_delta_at is not None:
|
|
806
826
|
# From the TURN's start, not this round's: the person has been
|
|
807
827
|
# waiting since they hit send, through the context re-render's
|
|
@@ -900,6 +920,17 @@ class ConversationFacilitator(SettingsAware):
|
|
|
900
920
|
|
|
901
921
|
# --- Internal helpers ---
|
|
902
922
|
|
|
923
|
+
def _forget_refused_turn(self, history_before: int) -> None:
|
|
924
|
+
"""Drop the refused turn from history, user message included.
|
|
925
|
+
|
|
926
|
+
A refused exchange left in history is the context the next request is
|
|
927
|
+
refused in again; the provider's guidance on a refusal is to change or
|
|
928
|
+
drop the turn that triggered it. The turn did not happen, so history
|
|
929
|
+
returns to where it stood before it — the host still has the person's
|
|
930
|
+
words and can offer them back for rephrasing.
|
|
931
|
+
"""
|
|
932
|
+
del self._messages[history_before:]
|
|
933
|
+
|
|
903
934
|
#: Answer written into the synthetic tool_result when the tool loop is cut
|
|
904
935
|
#: short. Addressed to the model, because the model reads it: it must
|
|
905
936
|
#: understand that the tool did not run and that it should wrap up with
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Provider verdicts that are not transport faults.
|
|
3
|
+
|
|
4
|
+
`ModelRefusal` is raised when the model stops with ``stop_reason: "refusal"``
|
|
5
|
+
(the provider's policy classifiers intervened). It is deliberately its own
|
|
6
|
+
type: before it existed a refusal reached the framework as an empty or cut-off
|
|
7
|
+
reply, so a structured call re-asked it as a ParseError (ten times) and a
|
|
8
|
+
conversational turn fell through to a second, structured call that was refused
|
|
9
|
+
again. A refusal is the provider's answer to THIS request, so nothing retries
|
|
10
|
+
it; the caller decides — a concern fails soft as on any error, a host shows the
|
|
11
|
+
person something and lets them rephrase.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from typing import Optional
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ModelRefusal(RuntimeError):
|
|
20
|
+
"""The model declined the request (``stop_reason: "refusal"``).
|
|
21
|
+
|
|
22
|
+
``category`` and ``explanation`` are the provider's ``stop_details`` when it
|
|
23
|
+
sends them (``"cyber"``, ``"bio"``, or None for an unnamed category); a
|
|
24
|
+
streamed round does not carry them and leaves both None. The explanation
|
|
25
|
+
text is not guaranteed stable by the provider — show it, never branch on it.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
model: str,
|
|
31
|
+
*,
|
|
32
|
+
category: Optional[str] = None,
|
|
33
|
+
explanation: Optional[str] = None,
|
|
34
|
+
) -> None:
|
|
35
|
+
self.model = model
|
|
36
|
+
self.category = category
|
|
37
|
+
self.explanation = explanation
|
|
38
|
+
detail = f" ({category})" if category else ""
|
|
39
|
+
super().__init__(
|
|
40
|
+
f"Model {model} refused the request{detail}"
|
|
41
|
+
+ (f": {explanation}" if explanation else "")
|
|
42
|
+
)
|
|
@@ -16,10 +16,16 @@ from mirascope.llm.responses import AsyncResponse, AsyncStreamResponse, Response
|
|
|
16
16
|
from typing_extensions import Unpack
|
|
17
17
|
|
|
18
18
|
from dialectical_framework.enums.di import DI
|
|
19
|
+
from dialectical_framework.exceptions.provider_errors import ModelRefusal
|
|
19
20
|
from dialectical_framework.settings import Settings
|
|
21
|
+
from dialectical_framework.utils.format_compat import (
|
|
22
|
+
learn_format_mode_from_error,
|
|
23
|
+
with_format_compat,
|
|
24
|
+
)
|
|
20
25
|
from dialectical_framework.utils.thinking_compat import (
|
|
21
26
|
learn_thinking_shape_from_error,
|
|
22
27
|
with_thinking_compat,
|
|
28
|
+
without_earlier_turn_thinking,
|
|
23
29
|
)
|
|
24
30
|
|
|
25
31
|
if TYPE_CHECKING:
|
|
@@ -288,6 +294,62 @@ def _bedrock_model_name(model_id: str) -> str:
|
|
|
288
294
|
return model_id.removeprefix("bedrock/").removeprefix("anthropic/")
|
|
289
295
|
|
|
290
296
|
|
|
297
|
+
class _LearningStreamManager:
|
|
298
|
+
"""`messages.stream(...)` that learns the thinking shape from a 400, once.
|
|
299
|
+
|
|
300
|
+
The request is made when the manager is ENTERED (Mirascope's decoder does
|
|
301
|
+
`async with` on the first chunk), before anything has been yielded, so a
|
|
302
|
+
rejected shape can be re-sent without replaying a token — and the streamed
|
|
303
|
+
conversational round is exactly where an unset level goes out as "off",
|
|
304
|
+
so without this a wrong name heuristic would fail every streamed turn until
|
|
305
|
+
some awaited call taught the process. Thinking only: a learned FORMAT mode
|
|
306
|
+
would change the response's parser, which the caller already holds.
|
|
307
|
+
"""
|
|
308
|
+
|
|
309
|
+
def __init__(
|
|
310
|
+
self, client: Any, kwargs: dict[str, Any], params: Mapping[str, Any]
|
|
311
|
+
) -> None:
|
|
312
|
+
self._client = client
|
|
313
|
+
self._kwargs = kwargs
|
|
314
|
+
self._params = params
|
|
315
|
+
self._manager: Any = None
|
|
316
|
+
|
|
317
|
+
def _open(self) -> Any:
|
|
318
|
+
return self._client.messages.stream(
|
|
319
|
+
**with_thinking_compat(self._kwargs["model"], self._kwargs, self._params)
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
async def __aenter__(self) -> Any:
|
|
323
|
+
self._manager = self._open()
|
|
324
|
+
try:
|
|
325
|
+
return await self._manager.__aenter__()
|
|
326
|
+
except Exception as e:
|
|
327
|
+
if not learn_thinking_shape_from_error(self._kwargs["model"], e):
|
|
328
|
+
raise
|
|
329
|
+
self._manager = self._open()
|
|
330
|
+
return await self._manager.__aenter__()
|
|
331
|
+
|
|
332
|
+
async def __aexit__(self, *exc: Any) -> Any:
|
|
333
|
+
return await self._manager.__aexit__(*exc)
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def raise_on_refusal(response: Any, model_name: str) -> None:
|
|
337
|
+
"""Raise `ModelRefusal` when the provider stopped on a refusal.
|
|
338
|
+
|
|
339
|
+
Raised at the provider, below `use_brain`, so no retry ladder ever re-asks
|
|
340
|
+
it (an unknown exception re-raises there) and the tool loop's resumes —
|
|
341
|
+
Mirascope's own requests, which reach this provider too — are covered.
|
|
342
|
+
"""
|
|
343
|
+
if getattr(response, "stop_reason", None) != "refusal":
|
|
344
|
+
return
|
|
345
|
+
details = getattr(response, "stop_details", None)
|
|
346
|
+
raise ModelRefusal(
|
|
347
|
+
model_name,
|
|
348
|
+
category=getattr(details, "category", None),
|
|
349
|
+
explanation=getattr(details, "explanation", None),
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
|
|
291
353
|
class BedrockAnthropicProvider(AnthropicProvider):
|
|
292
354
|
"""Mirascope v2 provider that routes through AnthropicBedrock client (async-native).
|
|
293
355
|
|
|
@@ -305,6 +367,53 @@ class BedrockAnthropicProvider(AnthropicProvider):
|
|
|
305
367
|
self.async_client = AsyncAnthropicBedrock(timeout=timeout)
|
|
306
368
|
self._beta_provider = None
|
|
307
369
|
|
|
370
|
+
@staticmethod
|
|
371
|
+
def _encode(
|
|
372
|
+
model_id: str,
|
|
373
|
+
messages: Sequence[Message],
|
|
374
|
+
toolkit: AsyncToolkit | Toolkit,
|
|
375
|
+
format: FormatSpec[FormattableT] | None,
|
|
376
|
+
params: Mapping[str, Any],
|
|
377
|
+
) -> tuple[Any, Any, dict[str, Any]]:
|
|
378
|
+
"""Encode a request in the formatting mode this model can take.
|
|
379
|
+
|
|
380
|
+
Forced tool use is re-asked as JSON for a model that refuses it (Claude
|
|
381
|
+
5.5); see format_compat. Encoding happens here, BEFORE the request is
|
|
382
|
+
built, because the mode decides the request's shape — tools and
|
|
383
|
+
tool_choice for one, system instructions for the other — and the
|
|
384
|
+
decoded response's parser follows the resolved format. Earlier turns'
|
|
385
|
+
thinking blocks are dropped here too, for a model that binds them to
|
|
386
|
+
their prefix (see `without_earlier_turn_thinking`).
|
|
387
|
+
|
|
388
|
+
A call moved OFF forced tool use is also sent with thinking off unless
|
|
389
|
+
it asked for thinking: forced tool choice is what kept every structured
|
|
390
|
+
call from thinking by default (0 thinking tokens with no parameter,
|
|
391
|
+
2026-09-24), and JSON mode with nothing sent is the provider's default,
|
|
392
|
+
thinking on. Without this every concern on a 5.5 model would think —
|
|
393
|
+
a cost, latency and behaviour change no figure was taken under.
|
|
394
|
+
`with_thinking_compat` turns "disabled" into the model's own off.
|
|
395
|
+
"""
|
|
396
|
+
model_name = _bedrock_model_name(model_id)
|
|
397
|
+
compatible = with_format_compat(model_name, format)
|
|
398
|
+
input_messages, resolved_format, kwargs = _utils.encode_request(
|
|
399
|
+
model_id=model_id,
|
|
400
|
+
messages=messages,
|
|
401
|
+
tools=toolkit,
|
|
402
|
+
format=compatible,
|
|
403
|
+
params=params,
|
|
404
|
+
)
|
|
405
|
+
kwargs["model"] = model_name
|
|
406
|
+
# A structured call is never a round of the tool loop: no tools, and in
|
|
407
|
+
# JSON mode extra system text, so even THIS turn's blocks are bound to a
|
|
408
|
+
# prefix the request no longer has. Drop them all there.
|
|
409
|
+
kwargs["messages"] = without_earlier_turn_thinking(
|
|
410
|
+
model_name, kwargs["messages"], every_turn=format is not None
|
|
411
|
+
)
|
|
412
|
+
if compatible is not format and "thinking" not in kwargs:
|
|
413
|
+
kwargs["thinking"] = {"type": "disabled"}
|
|
414
|
+
_fix_cache_breakpoints(kwargs)
|
|
415
|
+
return input_messages, resolved_format, kwargs
|
|
416
|
+
|
|
308
417
|
async def _create_async(
|
|
309
418
|
self, kwargs: dict[str, Any], params: Mapping[str, Any]
|
|
310
419
|
) -> AnthropicMessage:
|
|
@@ -353,18 +462,23 @@ class BedrockAnthropicProvider(AnthropicProvider):
|
|
|
353
462
|
**params: Unpack[Params],
|
|
354
463
|
) -> AsyncResponse | AsyncResponse[FormattableT]:
|
|
355
464
|
"""Always use standard path — bedrock doesn't support beta structured outputs."""
|
|
356
|
-
input_messages, resolved_format, kwargs =
|
|
357
|
-
model_id
|
|
358
|
-
messages=messages,
|
|
359
|
-
tools=toolkit,
|
|
360
|
-
format=format,
|
|
361
|
-
params=params,
|
|
362
|
-
)
|
|
363
|
-
kwargs["model"] = _bedrock_model_name(model_id)
|
|
364
|
-
_fix_cache_breakpoints(kwargs)
|
|
365
|
-
anthropic_response = cast(
|
|
366
|
-
AnthropicMessage, await self._create_async(kwargs, params)
|
|
465
|
+
input_messages, resolved_format, kwargs = self._encode(
|
|
466
|
+
model_id, messages, toolkit, format, params
|
|
367
467
|
)
|
|
468
|
+
try:
|
|
469
|
+
anthropic_response = cast(
|
|
470
|
+
AnthropicMessage, await self._create_async(kwargs, params)
|
|
471
|
+
)
|
|
472
|
+
except Exception as e:
|
|
473
|
+
if not learn_format_mode_from_error(kwargs["model"], format, e):
|
|
474
|
+
raise
|
|
475
|
+
input_messages, resolved_format, kwargs = self._encode(
|
|
476
|
+
model_id, messages, toolkit, format, params
|
|
477
|
+
)
|
|
478
|
+
anthropic_response = cast(
|
|
479
|
+
AnthropicMessage, await self._create_async(kwargs, params)
|
|
480
|
+
)
|
|
481
|
+
raise_on_refusal(anthropic_response, kwargs["model"])
|
|
368
482
|
include_thoughts = _utils.get_include_thoughts(params)
|
|
369
483
|
assistant_message, finish_reason, usage = _utils.decode_response(
|
|
370
484
|
anthropic_response, model_id, include_thoughts=include_thoughts
|
|
@@ -393,21 +507,10 @@ class BedrockAnthropicProvider(AnthropicProvider):
|
|
|
393
507
|
**params: Unpack[Params],
|
|
394
508
|
) -> AsyncStreamResponse | AsyncStreamResponse[FormattableT]:
|
|
395
509
|
"""Stream responses from Bedrock Anthropic."""
|
|
396
|
-
input_messages, resolved_format, kwargs =
|
|
397
|
-
model_id
|
|
398
|
-
messages=messages,
|
|
399
|
-
tools=toolkit,
|
|
400
|
-
format=format,
|
|
401
|
-
params=params,
|
|
510
|
+
input_messages, resolved_format, kwargs = self._encode(
|
|
511
|
+
model_id, messages, toolkit, format, params
|
|
402
512
|
)
|
|
403
|
-
|
|
404
|
-
_fix_cache_breakpoints(kwargs)
|
|
405
|
-
# Streaming cannot learn from a 400: the error surfaces when the caller
|
|
406
|
-
# consumes the iterator, by which point retrying would replay tokens
|
|
407
|
-
# already yielded. The name heuristic is applied, and a genuine mismatch
|
|
408
|
-
# raises to the caller.
|
|
409
|
-
kwargs = with_thinking_compat(kwargs["model"], kwargs, params)
|
|
410
|
-
anthropic_stream = self.async_client.messages.stream(**kwargs)
|
|
513
|
+
anthropic_stream = _LearningStreamManager(self.async_client, kwargs, params)
|
|
411
514
|
include_thoughts = _utils.get_include_thoughts(params)
|
|
412
515
|
chunk_iterator = _utils.decode_async_stream(
|
|
413
516
|
anthropic_stream, include_thoughts=include_thoughts
|
|
@@ -433,16 +536,23 @@ class BedrockAnthropicProvider(AnthropicProvider):
|
|
|
433
536
|
**params: Unpack[Params],
|
|
434
537
|
) -> Response | Response[FormattableT]:
|
|
435
538
|
"""Always use standard path — bedrock doesn't support beta structured outputs."""
|
|
436
|
-
input_messages, resolved_format, kwargs =
|
|
437
|
-
model_id
|
|
438
|
-
messages=messages,
|
|
439
|
-
tools=toolkit,
|
|
440
|
-
format=format,
|
|
441
|
-
params=params,
|
|
539
|
+
input_messages, resolved_format, kwargs = self._encode(
|
|
540
|
+
model_id, messages, toolkit, format, params
|
|
442
541
|
)
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
542
|
+
try:
|
|
543
|
+
anthropic_response = cast(
|
|
544
|
+
AnthropicMessage, self._create_sync(kwargs, params)
|
|
545
|
+
)
|
|
546
|
+
except Exception as e:
|
|
547
|
+
if not learn_format_mode_from_error(kwargs["model"], format, e):
|
|
548
|
+
raise
|
|
549
|
+
input_messages, resolved_format, kwargs = self._encode(
|
|
550
|
+
model_id, messages, toolkit, format, params
|
|
551
|
+
)
|
|
552
|
+
anthropic_response = cast(
|
|
553
|
+
AnthropicMessage, self._create_sync(kwargs, params)
|
|
554
|
+
)
|
|
555
|
+
raise_on_refusal(anthropic_response, kwargs["model"])
|
|
446
556
|
include_thoughts = _utils.get_include_thoughts(params)
|
|
447
557
|
assistant_message, finish_reason, usage = _utils.decode_response(
|
|
448
558
|
anthropic_response, model_id, include_thoughts=include_thoughts
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Per-model compatibility for how a STRUCTURED call asks for its DTO.
|
|
3
|
+
|
|
4
|
+
Mirascope's default formatting mode is forced tool use (``tool_choice`` of type
|
|
5
|
+
``tool``, or ``any`` with several tools), and every concern has always used it.
|
|
6
|
+
Claude 5.5 models refuse it on Bedrock — reported 2026-10-08 on Sonnet 5.5 and
|
|
7
|
+
Opus 5.5, and probed the same day on Fable 5.1 (Fable 5 still accepts it): ``tool_choice: type "tool" and "any" are not supported for this
|
|
8
|
+
model`` — so with a 5.5 model configured every structured call 400s: the
|
|
9
|
+
coherence judge, the tetrad writers, every concern. JSON mode works on both
|
|
10
|
+
(it asks for the JSON in the reply instead), and it is the shape the framework
|
|
11
|
+
already uses where a structured call must think (`format_mode="json"`).
|
|
12
|
+
|
|
13
|
+
The mode is therefore chosen by MODEL at the provider, the way
|
|
14
|
+
`thinking_compat` chooses the thinking shape: a request that asked for forced
|
|
15
|
+
tool use, explicitly or by default, is sent in JSON mode to a model that cannot
|
|
16
|
+
take it. A request that asked for another mode (json, strict, parser) is left
|
|
17
|
+
alone. Selection is by model name with a learned fallback — if the heuristic
|
|
18
|
+
misses a future model, its first 400 teaches the process and the call is
|
|
19
|
+
re-encoded once.
|
|
20
|
+
|
|
21
|
+
What this does NOT decide: whether JSON mode is the better default for models
|
|
22
|
+
that accept both. It parses as reliably on a DTO-shaped call and costs less
|
|
23
|
+
prefill (`probe_format_mode_thinking.py`), but the framework's measured figures
|
|
24
|
+
were taken on forced tool use, so switching every model is a measurement, not
|
|
25
|
+
a compatibility fix.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
import logging
|
|
31
|
+
import re
|
|
32
|
+
from typing import Any, Optional, get_origin
|
|
33
|
+
|
|
34
|
+
from mirascope import llm
|
|
35
|
+
from mirascope.llm.formatting.format import Format
|
|
36
|
+
|
|
37
|
+
logger = logging.getLogger(__name__)
|
|
38
|
+
|
|
39
|
+
#: The mode substituted for forced tool use on a model that refuses it.
|
|
40
|
+
JSON = "json"
|
|
41
|
+
|
|
42
|
+
#: Model name -> True when it was LEARNED (from a 400) to refuse forced tool use.
|
|
43
|
+
_LEARNED: dict[str, bool] = {}
|
|
44
|
+
|
|
45
|
+
#: ``claude-<family>-<major>[-<minor>]``. The minor is one or two digits NOT
|
|
46
|
+
#: followed by another digit, so a date suffix is never read as a minor:
|
|
47
|
+
#: ``claude-haiku-4-5-20251001`` is 4.5, ``claude-sonnet-5`` is 5.0,
|
|
48
|
+
#: ``claude-sonnet-5-5`` is 5.5, and ``claude-3-5-sonnet`` does not match.
|
|
49
|
+
_FAMILY_VERSION = re.compile(r"claude-([a-z]+)-(\d+)(?:-(\d{1,2})(?!\d))?")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def claude_version(model_name: str) -> Optional[tuple[int, int]]:
|
|
53
|
+
"""(major, minor) of a Claude 4+-style model name; None when unreadable."""
|
|
54
|
+
match = _FAMILY_VERSION.search(model_name)
|
|
55
|
+
if not match:
|
|
56
|
+
return None
|
|
57
|
+
return int(match.group(2)), int(match.group(3) or 0)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def claude_family(model_name: str) -> Optional[str]:
|
|
61
|
+
"""``sonnet`` / ``opus`` / ... of a Claude 4+-style model name."""
|
|
62
|
+
match = _FAMILY_VERSION.search(model_name)
|
|
63
|
+
return match.group(1) if match else None
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def refuses_forced_tool_use(model_name: str) -> bool:
|
|
67
|
+
"""True when this model rejects ``tool_choice`` of type ``tool``/``any``."""
|
|
68
|
+
if model_name in _LEARNED:
|
|
69
|
+
return _LEARNED[model_name]
|
|
70
|
+
version = claude_version(model_name)
|
|
71
|
+
if version is None:
|
|
72
|
+
return False
|
|
73
|
+
# Probed 2026-10-08: Sonnet/Opus 5.5 and Fable 5.1 refuse; Fable 5 accepts.
|
|
74
|
+
if claude_family(model_name) in ("fable", "mythos"):
|
|
75
|
+
return version >= (5, 1)
|
|
76
|
+
return version >= (5, 5)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _forced_tool(format: Any) -> Any:
|
|
80
|
+
"""The formattable when `format` asks for forced tool use, else None.
|
|
81
|
+
|
|
82
|
+
A bare DTO class resolves to Mirascope's default mode, which is ``tool``; a
|
|
83
|
+
`Format` says its own mode. An `OutputParser` is never forced tool use.
|
|
84
|
+
"""
|
|
85
|
+
if isinstance(format, Format):
|
|
86
|
+
return format.formattable if format.mode == "tool" else None
|
|
87
|
+
# A class, or a parameterised generic (`list[str]`), which is not a `type`
|
|
88
|
+
# but resolves to the default mode exactly as one does.
|
|
89
|
+
if isinstance(format, type) or get_origin(format) is not None:
|
|
90
|
+
return format
|
|
91
|
+
return None
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def with_format_compat(model_name: str, format: Any) -> Any:
|
|
95
|
+
"""`format` as this model can take it. Pure; None passes through."""
|
|
96
|
+
if format is None or not refuses_forced_tool_use(model_name):
|
|
97
|
+
return format
|
|
98
|
+
formattable = _forced_tool(format)
|
|
99
|
+
if formattable is None:
|
|
100
|
+
return format
|
|
101
|
+
return llm.format(formattable, mode=JSON)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def learn_format_mode_from_error(
|
|
105
|
+
model_name: str, format: Any, error: BaseException
|
|
106
|
+
) -> bool:
|
|
107
|
+
"""Record that this model refuses forced tool use. True if worth retrying once.
|
|
108
|
+
|
|
109
|
+
Only when the failed request asked for forced tool use and the error says
|
|
110
|
+
so; every other error returns False and surfaces unchanged.
|
|
111
|
+
"""
|
|
112
|
+
if format is None or _forced_tool(format) is None:
|
|
113
|
+
return False
|
|
114
|
+
message = str(error)
|
|
115
|
+
# The provider's text: 'tool_choice: type "tool" and "any" are not
|
|
116
|
+
# supported for this model.' Matched on the forced types, so a 400 about
|
|
117
|
+
# some other tool_choice field cannot move a capable model to JSON.
|
|
118
|
+
if "tool_choice" not in message or "not supported" not in message:
|
|
119
|
+
return False
|
|
120
|
+
if '"tool"' not in message and '"any"' not in message:
|
|
121
|
+
return False
|
|
122
|
+
if refuses_forced_tool_use(model_name):
|
|
123
|
+
# Already sent as JSON and it still failed — the mode is not the problem.
|
|
124
|
+
return False
|
|
125
|
+
_LEARNED[model_name] = True
|
|
126
|
+
logger.warning(
|
|
127
|
+
"Model %s rejected forced tool use; structured calls go in JSON mode for "
|
|
128
|
+
"the rest of this process. Adjust format_compat if this is a naming gap.",
|
|
129
|
+
model_name,
|
|
130
|
+
)
|
|
131
|
+
return True
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def reset_learned_format_modes() -> None:
|
|
135
|
+
"""Test seam — the learned map is process-global by design."""
|
|
136
|
+
_LEARNED.clear()
|