lionagi 0.14.4__tar.gz → 0.14.6__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.
- {lionagi-0.14.4 → lionagi-0.14.6}/PKG-INFO +6 -2
- lionagi-0.14.6/docs/discussions/anyio-migration.md +178 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/docs/modules/branch_operations.rst +1 -3
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/fields/instruct.py +3 -17
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/concurrency/__init__.py +25 -1
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/concurrency/cancel.py +1 -1
- lionagi-0.14.6/lionagi/libs/concurrency/patterns.py +259 -0
- lionagi-0.14.6/lionagi/libs/concurrency/primitives.py +290 -0
- lionagi-0.14.6/lionagi/libs/concurrency/resource_tracker.py +182 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/concurrency/task.py +4 -2
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/builder.py +9 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/flow.py +163 -60
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/generic/pile.py +7 -10
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/generic/processor.py +53 -26
- lionagi-0.14.6/lionagi/service/connections/providers/_claude_code/__init__.py +3 -0
- lionagi-0.14.6/lionagi/service/connections/providers/_claude_code/models.py +235 -0
- lionagi-0.14.4/lionagi/service/connections/providers/claude_code_cli.py → lionagi-0.14.6/lionagi/service/connections/providers/_claude_code/stream_cli.py +20 -80
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/service/connections/providers/claude_code_.py +13 -223
- lionagi-0.14.6/lionagi/service/connections/providers/claude_code_cli.py +105 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/service/rate_limited_processor.py +53 -35
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/session/branch.py +6 -51
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/session/session.py +26 -8
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/utils.py +56 -174
- lionagi-0.14.6/lionagi/version.py +1 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/pyproject.toml +7 -2
- {lionagi-0.14.4 → lionagi-0.14.6}/uv.lock +11 -4
- lionagi-0.14.4/lionagi/libs/concurrency/patterns.py +0 -252
- lionagi-0.14.4/lionagi/libs/concurrency/primitives.py +0 -242
- lionagi-0.14.4/lionagi/version.py +0 -1
- {lionagi-0.14.4 → lionagi-0.14.6}/.coveragerc +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/.env.example +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/.github/FUNDING.yml +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/.github/dependabot.yml +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/.github/workflows/ci.yml +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/.github/workflows/codeql.yml +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/.github/workflows/docs.yml +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/.github/workflows/release.yml +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/.gitignore +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/.pre-commit-config.yaml +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/.python-version +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/CODE_OF_CONDUCT.md +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/CONTRIBUTING.md +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/LICENSE +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/README.md +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/assets/operation_builder.gif +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/cookbooks/001_branch_converse.ipynb +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/cookbooks/002_branch_interact.ipynb +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/cookbooks/003_branch_info.ipynb +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/cookbooks/004_conversation_patterns.ipynb +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/cookbooks/005_react_basics.ipynb +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/cookbooks/006_operation_graphs_claim_extraction.ipynb +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/cookbooks/data/002_comedian.json +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/cookbooks/data/002_critic.json +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/cookbooks/data/006_lion_proof_ch2.md +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/cookbooks/using_claude_code.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/docs/Makefile +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/docs/_static/custom.css +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/docs/_templates/layout.html +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/docs/conf.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/docs/index.rst +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/docs/modules/action.rst +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/docs/modules/adapter.rst +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/docs/modules/branch.rst +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/docs/modules/concepts.rst +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/docs/modules/element_id.rst +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/docs/modules/event.rst +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/docs/modules/form.rst +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/docs/modules/graph.rst +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/docs/modules/index.rst +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/docs/modules/instruct.rst +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/docs/modules/lib_file.rst +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/docs/modules/lib_nested.rst +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/docs/modules/lib_package.rst +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/docs/modules/lib_schema.rst +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/docs/modules/lib_validate.rst +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/docs/modules/log.rst +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/docs/modules/mail.rst +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/docs/modules/message.rst +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/docs/modules/models.rst +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/docs/modules/operative_step.rst +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/docs/modules/pile.rst +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/docs/modules/processor.rst +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/docs/modules/progression.rst +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/docs/modules/service.rst +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/docs/modules/session.rst +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/docs/modules/utils.rst +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/docs/tutorials/get_started.rst +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/docs/tutorials/get_started_pt2.rst +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/docs/tutorials/get_started_pt3.rst +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/docs/tutorials/index.rst +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/_class_registry.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/_errors.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/_types.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/adapters/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/adapters/async_postgres_adapter.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/adapters/postgres_model_adapter.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/config.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/fields/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/fields/action.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/fields/base.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/fields/code.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/fields/file.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/fields/reason.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/fields/research.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/concurrency/errors.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/file/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/file/chunk.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/file/concat.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/file/concat_files.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/file/file_ops.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/file/params.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/file/process.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/file/save.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/nested/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/nested/flatten.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/nested/nfilter.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/nested/nget.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/nested/ninsert.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/nested/nmerge.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/nested/npop.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/nested/nset.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/nested/unflatten.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/nested/utils.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/package/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/package/imports.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/package/management.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/package/params.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/package/system.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/parse.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/schema/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/schema/as_readable.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/schema/extract_code_block.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/schema/extract_docstring.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/schema/function_to_schema.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/schema/json_schema.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/schema/load_pydantic_model_from_schema.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/token_transform/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/token_transform/base.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/token_transform/llmlingua.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/token_transform/perplexity.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/token_transform/symbolic_compress_context.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/token_transform/synthlang.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/token_transform/synthlang_/base.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/token_transform/synthlang_/resources/frameworks/abstract_algebra.toml +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/token_transform/synthlang_/resources/frameworks/category_theory.toml +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/token_transform/synthlang_/resources/frameworks/complex_analysis.toml +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/token_transform/synthlang_/resources/frameworks/framework_options.json +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/token_transform/synthlang_/resources/frameworks/group_theory.toml +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/token_transform/synthlang_/resources/frameworks/math_logic.toml +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/token_transform/synthlang_/resources/frameworks/reflective_patterns.toml +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/token_transform/synthlang_/resources/frameworks/set_theory.toml +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/token_transform/synthlang_/resources/frameworks/topology_fundamentals.toml +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/token_transform/synthlang_/resources/mapping/lion_emoji_mapping.toml +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/token_transform/synthlang_/resources/mapping/python_math_mapping.toml +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/token_transform/synthlang_/resources/mapping/rust_chinese_mapping.toml +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/token_transform/synthlang_/resources/utility/base_synthlang_system_prompt.toml +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/token_transform/synthlang_/translate_to_synthlang.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/token_transform/types.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/validate/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/validate/common_field_validators.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/validate/fuzzy_match_keys.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/validate/fuzzy_validate_mapping.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/validate/string_similarity.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/libs/validate/validate_boolean.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/models/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/models/field_model.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/models/hashable_model.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/models/model_params.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/models/note.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/models/operable_model.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/models/schema_model.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/ReAct/ReAct.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/ReAct/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/ReAct/utils.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/_act/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/_act/act.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/brainstorm/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/brainstorm/brainstorm.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/brainstorm/prompt.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/chat/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/chat/chat.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/communicate/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/communicate/communicate.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/instruct/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/instruct/instruct.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/interpret/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/interpret/interpret.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/manager.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/node.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/operate/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/operate/operate.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/parse/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/parse/parse.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/plan/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/plan/plan.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/plan/prompt.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/select/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/select/select.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/select/utils.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/translate/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/translate/translate.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/types.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/operations/utils.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/_concepts.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/action/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/action/function_calling.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/action/manager.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/action/tool.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/forms/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/forms/base.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/forms/flow.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/forms/form.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/forms/report.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/generic/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/generic/element.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/generic/event.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/generic/log.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/generic/progression.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/graph/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/graph/edge.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/graph/graph.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/graph/node.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/mail/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/mail/exchange.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/mail/mail.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/mail/mailbox.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/mail/manager.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/mail/package.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/messages/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/messages/action_request.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/messages/action_response.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/messages/assistant_response.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/messages/base.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/messages/instruction.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/messages/manager.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/messages/message.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/messages/system.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/messages/templates/README.md +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/messages/templates/action_request.jinja2 +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/messages/templates/action_response.jinja2 +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/messages/templates/assistant_response.jinja2 +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/messages/templates/instruction_message.jinja2 +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/messages/templates/system_message.jinja2 +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/messages/templates/tool_schemas.jinja2 +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/operatives/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/operatives/operative.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/operatives/step.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/protocols/types.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/py.typed +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/service/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/service/connections/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/service/connections/api_calling.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/service/connections/endpoint.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/service/connections/endpoint_config.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/service/connections/header_factory.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/service/connections/match_endpoint.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/service/connections/providers/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/service/connections/providers/anthropic_.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/service/connections/providers/exa_.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/service/connections/providers/oai_.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/service/connections/providers/ollama_.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/service/connections/providers/perplexity_.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/service/imodel.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/service/manager.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/service/resilience.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/service/third_party/README.md +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/service/third_party/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/service/third_party/anthropic_models.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/service/third_party/exa_models.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/service/third_party/openai_models.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/service/third_party/pplx_models.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/service/token_calculator.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/service/types.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/session/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/session/prompts.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/settings.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/tools/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/tools/base.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/tools/file/__init__.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/tools/file/reader.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/tools/memory/tools.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/lionagi/tools/types.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/main.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/scripts/README.md +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/scripts/concat.py +0 -0
- {lionagi-0.14.4 → lionagi-0.14.6}/scripts/config.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: lionagi
|
3
|
-
Version: 0.14.
|
3
|
+
Version: 0.14.6
|
4
4
|
Summary: An Intelligence Operating System.
|
5
5
|
Author-email: HaiyangLi <quantocean.li@gmail.com>, Liangbingyan Luo <llby_luo@outlook.com>
|
6
6
|
License: Apache License
|
@@ -225,7 +225,6 @@ Requires-Dist: anyio>=4.8.0
|
|
225
225
|
Requires-Dist: backoff>=2.2.1
|
226
226
|
Requires-Dist: jinja2>=3.1.0
|
227
227
|
Requires-Dist: json-repair>=0.40.0
|
228
|
-
Requires-Dist: matplotlib>=3.9.0
|
229
228
|
Requires-Dist: pillow>=11.0.0
|
230
229
|
Requires-Dist: psutil>=7.0.0
|
231
230
|
Requires-Dist: pydantic-settings>=2.8.0
|
@@ -239,6 +238,8 @@ Requires-Dist: claude-code-sdk>=0.0.14; extra == 'all'
|
|
239
238
|
Requires-Dist: datamodel-code-generator>=0.31.2; extra == 'all'
|
240
239
|
Requires-Dist: docling>=2.15.1; extra == 'all'
|
241
240
|
Requires-Dist: fastmcp>=2.10.5; extra == 'all'
|
241
|
+
Requires-Dist: matplotlib>=3.7.0; extra == 'all'
|
242
|
+
Requires-Dist: networkx>=3.0.0; extra == 'all'
|
242
243
|
Requires-Dist: ollama>=0.4.0; extra == 'all'
|
243
244
|
Requires-Dist: pydapter[postgres]; extra == 'all'
|
244
245
|
Requires-Dist: rich>=13.0.0; extra == 'all'
|
@@ -248,6 +249,9 @@ Provides-Extra: docs
|
|
248
249
|
Requires-Dist: furo>=2024.8.6; extra == 'docs'
|
249
250
|
Requires-Dist: sphinx-autobuild>=2024.10.3; extra == 'docs'
|
250
251
|
Requires-Dist: sphinx>=8.1.3; extra == 'docs'
|
252
|
+
Provides-Extra: graph
|
253
|
+
Requires-Dist: matplotlib>=3.7.0; extra == 'graph'
|
254
|
+
Requires-Dist: networkx>=3.0.0; extra == 'graph'
|
251
255
|
Provides-Extra: lint
|
252
256
|
Requires-Dist: black[jupyter]>=24.10.0; extra == 'lint'
|
253
257
|
Requires-Dist: isort>=5.13.2; extra == 'lint'
|
@@ -0,0 +1,178 @@
|
|
1
|
+
# Migration to AnyIO: A Technical Discussion
|
2
|
+
|
3
|
+
## Overview
|
4
|
+
|
5
|
+
In our latest release (v0.14.5), we've made a strategic decision to migrate our asynchronous primitives from raw `asyncio` to `anyio`. This document discusses the rationale, implications, and future benefits of this architectural change.
|
6
|
+
|
7
|
+
## Why AnyIO?
|
8
|
+
|
9
|
+
### 1. Backend Agnosticism
|
10
|
+
|
11
|
+
AnyIO provides a unified interface that works with multiple async backends:
|
12
|
+
- **asyncio** (current Python standard)
|
13
|
+
- **trio** (structured concurrency pioneer)
|
14
|
+
- **curio** (high-performance alternative)
|
15
|
+
|
16
|
+
This abstraction layer means our codebase is no longer tightly coupled to asyncio's implementation details. As the Python async ecosystem evolves, we can adapt without major rewrites.
|
17
|
+
|
18
|
+
### 2. Structured Concurrency by Default
|
19
|
+
|
20
|
+
```python
|
21
|
+
# Before (asyncio)
|
22
|
+
tasks = []
|
23
|
+
for item in items:
|
24
|
+
task = asyncio.create_task(process(item))
|
25
|
+
tasks.append(task)
|
26
|
+
results = await asyncio.gather(*tasks) # What if a task fails?
|
27
|
+
|
28
|
+
# After (anyio)
|
29
|
+
async with create_task_group() as tg:
|
30
|
+
for item in items:
|
31
|
+
await tg.start_soon(process, item)
|
32
|
+
# All tasks are guaranteed to complete or cancel properly
|
33
|
+
```
|
34
|
+
|
35
|
+
Task groups enforce a fundamental principle: **no task outlives its parent scope**. This eliminates entire classes of bugs related to orphaned tasks and resource leaks.
|
36
|
+
|
37
|
+
### 3. Better Cancellation Semantics
|
38
|
+
|
39
|
+
AnyIO's cancellation model is more predictable:
|
40
|
+
- Cancellation is always delivered at checkpoint
|
41
|
+
- Cancel scopes provide fine-grained control
|
42
|
+
- No more mysterious "Task was destroyed but it is pending!" errors
|
43
|
+
|
44
|
+
```python
|
45
|
+
# Timeout with proper cancellation
|
46
|
+
with anyio.move_on_after(5.0) as cancel_scope:
|
47
|
+
result = await long_running_operation()
|
48
|
+
if cancel_scope.cancelled_caught:
|
49
|
+
print("Operation timed out cleanly")
|
50
|
+
```
|
51
|
+
|
52
|
+
## Implementation Highlights
|
53
|
+
|
54
|
+
### Thread-to-Async Bridge
|
55
|
+
|
56
|
+
One subtle but important change is how we handle sync functions in async contexts:
|
57
|
+
|
58
|
+
```python
|
59
|
+
# Before
|
60
|
+
result = await asyncio.to_thread(sync_func, arg)
|
61
|
+
|
62
|
+
# After
|
63
|
+
result = await anyio.to_thread.run_sync(sync_func, arg)
|
64
|
+
```
|
65
|
+
|
66
|
+
AnyIO's approach provides:
|
67
|
+
- Better thread pool management
|
68
|
+
- Proper cancellation propagation to threads
|
69
|
+
- Consistent behavior across different async backends
|
70
|
+
|
71
|
+
### Unified Lock Semantics
|
72
|
+
|
73
|
+
Our `ConcurrencyLock` now wraps AnyIO's primitives, providing:
|
74
|
+
- Same lock works in both sync and async contexts
|
75
|
+
- No more deadlocks from mixing lock types
|
76
|
+
- Better debugging with lock ownership tracking
|
77
|
+
|
78
|
+
### Sleep and Timing
|
79
|
+
|
80
|
+
```python
|
81
|
+
# Before
|
82
|
+
await asyncio.sleep(1.0)
|
83
|
+
|
84
|
+
# After
|
85
|
+
await anyio.sleep(1.0)
|
86
|
+
```
|
87
|
+
|
88
|
+
While seemingly trivial, AnyIO's sleep:
|
89
|
+
- Respects cancellation properly
|
90
|
+
- Works consistently across backends
|
91
|
+
- Integrates with structured concurrency
|
92
|
+
|
93
|
+
## Performance Implications
|
94
|
+
|
95
|
+
### The Good
|
96
|
+
1. **Better CPU utilization**: Task groups reduce overhead compared to gather/wait
|
97
|
+
2. **Memory efficiency**: Structured concurrency prevents task accumulation
|
98
|
+
3. **Predictable performance**: Consistent behavior reduces edge-case slowdowns
|
99
|
+
|
100
|
+
### The Trade-offs
|
101
|
+
1. **Slight overhead**: Abstraction layer adds minimal overhead (~5-10%)
|
102
|
+
2. **Learning curve**: Developers need to understand structured concurrency
|
103
|
+
3. **Ecosystem compatibility**: Some asyncio-specific libraries need adapters
|
104
|
+
|
105
|
+
## Future Benefits
|
106
|
+
|
107
|
+
### 1. Trio Compatibility
|
108
|
+
We can now experiment with trio as a backend for specific use cases:
|
109
|
+
```python
|
110
|
+
# Run lionagi with trio backend
|
111
|
+
import trio
|
112
|
+
import anyio
|
113
|
+
|
114
|
+
async def main():
|
115
|
+
async with anyio.from_thread.start_blocking_portal(
|
116
|
+
backend="trio", backend_options={"trio_token": trio.lowlevel.current_trio_token()}
|
117
|
+
) as portal:
|
118
|
+
await portal.call(run_lionagi_operation)
|
119
|
+
```
|
120
|
+
|
121
|
+
### 2. Better Testing
|
122
|
+
AnyIO provides excellent testing utilities:
|
123
|
+
```python
|
124
|
+
async def test_timeout_handling():
|
125
|
+
with anyio.move_on_after(0.1):
|
126
|
+
await anyio.sleep(1.0) # Will be cancelled
|
127
|
+
# Test continues normally
|
128
|
+
```
|
129
|
+
|
130
|
+
### 3. WebAssembly Ready
|
131
|
+
As Python moves toward WASM support, AnyIO's abstraction will help us adapt to environments where traditional threading doesn't exist.
|
132
|
+
|
133
|
+
## Migration Patterns
|
134
|
+
|
135
|
+
### For Library Users
|
136
|
+
Most changes are transparent, but be aware of:
|
137
|
+
- Different exception types (use `anyio.get_cancelled_exc_class()`)
|
138
|
+
- Import changes if you were using our internal async utilities
|
139
|
+
- Slightly different timeout behavior (more predictable)
|
140
|
+
|
141
|
+
### for Library Developers
|
142
|
+
Key patterns to adopt:
|
143
|
+
```python
|
144
|
+
# Always use task groups for concurrent operations
|
145
|
+
async with create_task_group() as tg:
|
146
|
+
await tg.start_soon(task1)
|
147
|
+
await tg.start_soon(task2)
|
148
|
+
|
149
|
+
# Use cancel scopes for timeouts
|
150
|
+
with anyio.move_on_after(timeout):
|
151
|
+
result = await operation()
|
152
|
+
|
153
|
+
# Handle backend-specific exceptions
|
154
|
+
try:
|
155
|
+
await operation()
|
156
|
+
except anyio.get_cancelled_exc_class():
|
157
|
+
# Properly handle cancellation
|
158
|
+
raise
|
159
|
+
```
|
160
|
+
|
161
|
+
## Philosophical Alignment
|
162
|
+
|
163
|
+
This migration aligns with our core principles:
|
164
|
+
|
165
|
+
1. **Reliability First**: Structured concurrency eliminates entire bug categories
|
166
|
+
2. **Future-Proof Design**: Backend agnosticism protects against ecosystem changes
|
167
|
+
3. **Developer Experience**: Clearer mental models reduce cognitive load
|
168
|
+
4. **Performance with Correctness**: We optimize without sacrificing safety
|
169
|
+
|
170
|
+
## Conclusion
|
171
|
+
|
172
|
+
The migration to AnyIO represents a maturation of our async architecture. While the immediate benefits include better resource management and fewer concurrency bugs, the long-term value lies in our ability to evolve with the Python async ecosystem.
|
173
|
+
|
174
|
+
This change positions lionagi to take advantage of future innovations in async Python while providing our users with a more reliable and predictable experience today.
|
175
|
+
|
176
|
+
---
|
177
|
+
|
178
|
+
*For specific migration examples and API changes, see our [v0.14.6 Release Notes](./RELEASE_NOTES_v0.14.5.md).*
|
@@ -64,7 +64,7 @@ of action requests. Optionally retries, logs errors, or merges results.
|
|
64
64
|
This is best used for **explicit function calls** triggered from user
|
65
65
|
instructions or system logic.
|
66
66
|
|
67
|
-
.. method:: Branch.act(action_request, *, suppress_errors=True, sanitize_input=False, unique_input=False, num_retries=0, initial_delay=0, retry_delay=0, backoff_factor=1, retry_default=UNDEFINED, retry_timeout=None,
|
67
|
+
.. method:: Branch.act(action_request, *, suppress_errors=True, sanitize_input=False, unique_input=False, num_retries=0, initial_delay=0, retry_delay=0, backoff_factor=1, retry_default=UNDEFINED, retry_timeout=None, max_concurrent=None, throttle_period=None, flatten=True, dropna=True, unique_output=False, flatten_tuple_set=False)
|
68
68
|
|
69
69
|
Public, potentially batched, asynchronous interface to run one or multiple action requests.
|
70
70
|
|
@@ -90,8 +90,6 @@ instructions or system logic.
|
|
90
90
|
Fallback value if all retries fail (if suppressing errors)
|
91
91
|
retry_timeout : float | None, default=None
|
92
92
|
Overall timeout for all attempts (None = no limit)
|
93
|
-
retry_timing : bool, default=False
|
94
|
-
If True, track time used for retries
|
95
93
|
max_concurrent : int | None, default=None
|
96
94
|
Maximum concurrent tasks (if batching)
|
97
95
|
throttle_period : float | None, default=None
|
@@ -35,7 +35,6 @@ class Instruct(HashableModel):
|
|
35
35
|
"reason",
|
36
36
|
"actions",
|
37
37
|
"action_strategy",
|
38
|
-
"batch_size",
|
39
38
|
"request_params",
|
40
39
|
"response_params",
|
41
40
|
]
|
@@ -97,16 +96,10 @@ class Instruct(HashableModel):
|
|
97
96
|
"None: Contextual execution."
|
98
97
|
),
|
99
98
|
)
|
100
|
-
action_strategy: Literal["
|
101
|
-
Field(
|
102
|
-
None,
|
103
|
-
description="Action strategy to use for executing actions. Default "
|
104
|
-
"is 'concurrent'. Only provide for if actions are enabled.",
|
105
|
-
)
|
106
|
-
)
|
107
|
-
batch_size: int | None = Field(
|
99
|
+
action_strategy: Literal["sequential", "concurrent"] | None = Field(
|
108
100
|
None,
|
109
|
-
description="
|
101
|
+
description="Action strategy to use for executing actions. Default "
|
102
|
+
"is 'concurrent'. Only provide for if actions are enabled.",
|
110
103
|
)
|
111
104
|
|
112
105
|
@field_validator("instruction", "guidance", "context", mode="before")
|
@@ -123,13 +116,6 @@ class Instruct(HashableModel):
|
|
123
116
|
return "concurrent"
|
124
117
|
return v
|
125
118
|
|
126
|
-
@field_validator("batch_size", mode="before")
|
127
|
-
def _validate_batch_size(cls, v):
|
128
|
-
try:
|
129
|
-
return to_num(v, num_type=int)
|
130
|
-
except Exception:
|
131
|
-
return None
|
132
|
-
|
133
119
|
|
134
120
|
class InstructResponse(HashableModel):
|
135
121
|
instruct: Instruct
|
@@ -1,4 +1,4 @@
|
|
1
|
-
"""Structured concurrency primitives.
|
1
|
+
"""Structured concurrency primitives for pynector.
|
2
2
|
|
3
3
|
This module provides structured concurrency primitives using AnyIO,
|
4
4
|
which allows for consistent behavior across asyncio and trio backends.
|
@@ -6,7 +6,21 @@ which allows for consistent behavior across asyncio and trio backends.
|
|
6
6
|
|
7
7
|
from .cancel import CancelScope, fail_after, move_on_after
|
8
8
|
from .errors import get_cancelled_exc_class, shield
|
9
|
+
from .patterns import (
|
10
|
+
ConnectionPool,
|
11
|
+
WorkerPool,
|
12
|
+
parallel_requests,
|
13
|
+
retry_with_timeout,
|
14
|
+
)
|
9
15
|
from .primitives import CapacityLimiter, Condition, Event, Lock, Semaphore
|
16
|
+
from .resource_tracker import (
|
17
|
+
ResourceTracker,
|
18
|
+
cleanup_check,
|
19
|
+
get_global_tracker,
|
20
|
+
resource_leak_detector,
|
21
|
+
track_resource,
|
22
|
+
untrack_resource,
|
23
|
+
)
|
10
24
|
from .task import TaskGroup, create_task_group
|
11
25
|
|
12
26
|
__all__ = [
|
@@ -15,6 +29,10 @@ __all__ = [
|
|
15
29
|
"CancelScope",
|
16
30
|
"move_on_after",
|
17
31
|
"fail_after",
|
32
|
+
"ConnectionPool",
|
33
|
+
"WorkerPool",
|
34
|
+
"parallel_requests",
|
35
|
+
"retry_with_timeout",
|
18
36
|
"Lock",
|
19
37
|
"Semaphore",
|
20
38
|
"CapacityLimiter",
|
@@ -22,4 +40,10 @@ __all__ = [
|
|
22
40
|
"Condition",
|
23
41
|
"get_cancelled_exc_class",
|
24
42
|
"shield",
|
43
|
+
"ResourceTracker",
|
44
|
+
"resource_leak_detector",
|
45
|
+
"track_resource",
|
46
|
+
"untrack_resource",
|
47
|
+
"cleanup_check",
|
48
|
+
"get_global_tracker",
|
25
49
|
]
|
@@ -0,0 +1,259 @@
|
|
1
|
+
"""Common concurrency patterns for structured concurrency."""
|
2
|
+
|
3
|
+
from __future__ import annotations
|
4
|
+
|
5
|
+
import logging
|
6
|
+
from collections.abc import Awaitable, Callable
|
7
|
+
from types import TracebackType
|
8
|
+
from typing import Any, TypeVar
|
9
|
+
|
10
|
+
import anyio
|
11
|
+
|
12
|
+
from .cancel import move_on_after
|
13
|
+
from .primitives import CapacityLimiter, Lock
|
14
|
+
from .resource_tracker import track_resource, untrack_resource
|
15
|
+
from .task import create_task_group
|
16
|
+
|
17
|
+
logger = logging.getLogger(__name__)
|
18
|
+
|
19
|
+
T = TypeVar("T")
|
20
|
+
R = TypeVar("R")
|
21
|
+
Response = TypeVar("Response")
|
22
|
+
|
23
|
+
|
24
|
+
class ConnectionPool:
|
25
|
+
"""A pool of reusable connections."""
|
26
|
+
|
27
|
+
def __init__(
|
28
|
+
self,
|
29
|
+
max_connections: int,
|
30
|
+
connection_factory: Callable[[], Awaitable[T]],
|
31
|
+
):
|
32
|
+
"""Initialize a new connection pool."""
|
33
|
+
if max_connections < 1:
|
34
|
+
raise ValueError("max_connections must be >= 1")
|
35
|
+
if not callable(connection_factory):
|
36
|
+
raise ValueError("connection_factory must be callable")
|
37
|
+
|
38
|
+
self._connection_factory = connection_factory
|
39
|
+
self._limiter = CapacityLimiter(max_connections)
|
40
|
+
self._connections: list[T] = []
|
41
|
+
self._lock = Lock()
|
42
|
+
|
43
|
+
track_resource(self, f"ConnectionPool-{id(self)}", "ConnectionPool")
|
44
|
+
|
45
|
+
def __del__(self):
|
46
|
+
"""Clean up resource tracking."""
|
47
|
+
try:
|
48
|
+
untrack_resource(self)
|
49
|
+
except Exception:
|
50
|
+
pass
|
51
|
+
|
52
|
+
async def acquire(self) -> T:
|
53
|
+
"""Acquire a connection from the pool."""
|
54
|
+
await self._limiter.acquire()
|
55
|
+
|
56
|
+
try:
|
57
|
+
async with self._lock:
|
58
|
+
if self._connections:
|
59
|
+
return self._connections.pop()
|
60
|
+
|
61
|
+
# No pooled connection available, create new one
|
62
|
+
return await self._connection_factory()
|
63
|
+
except Exception:
|
64
|
+
self._limiter.release()
|
65
|
+
raise
|
66
|
+
|
67
|
+
async def release(self, connection: T) -> None:
|
68
|
+
"""Release a connection back to the pool."""
|
69
|
+
try:
|
70
|
+
async with self._lock:
|
71
|
+
self._connections.append(connection)
|
72
|
+
finally:
|
73
|
+
self._limiter.release()
|
74
|
+
|
75
|
+
async def __aenter__(self) -> ConnectionPool[T]:
|
76
|
+
"""Enter the connection pool context."""
|
77
|
+
return self
|
78
|
+
|
79
|
+
async def __aexit__(
|
80
|
+
self,
|
81
|
+
exc_type: type[BaseException] | None,
|
82
|
+
exc_val: BaseException | None,
|
83
|
+
exc_tb: TracebackType | None,
|
84
|
+
) -> None:
|
85
|
+
"""Exit the connection pool context."""
|
86
|
+
# Clean up any remaining connections
|
87
|
+
async with self._lock:
|
88
|
+
self._connections.clear()
|
89
|
+
|
90
|
+
|
91
|
+
async def parallel_requests(
|
92
|
+
inputs: list[str],
|
93
|
+
func: Callable[[str], Awaitable[Response]],
|
94
|
+
max_concurrency: int = 10,
|
95
|
+
) -> list[Response]:
|
96
|
+
"""Execute requests in parallel with controlled concurrency.
|
97
|
+
|
98
|
+
Args:
|
99
|
+
inputs: List of inputs
|
100
|
+
fetch_func: Async function
|
101
|
+
max_concurrency: Maximum number of concurrent requests
|
102
|
+
|
103
|
+
Returns:
|
104
|
+
List of responses in the same order as inputs
|
105
|
+
"""
|
106
|
+
if not inputs:
|
107
|
+
return []
|
108
|
+
|
109
|
+
results: list[Response | None] = [None] * len(inputs)
|
110
|
+
|
111
|
+
async def bounded_fetch(
|
112
|
+
semaphore: anyio.Semaphore, idx: int, url: str
|
113
|
+
) -> None:
|
114
|
+
async with semaphore:
|
115
|
+
results[idx] = await func(url)
|
116
|
+
|
117
|
+
try:
|
118
|
+
async with create_task_group() as tg:
|
119
|
+
semaphore = anyio.Semaphore(max_concurrency)
|
120
|
+
|
121
|
+
for i, inp in enumerate(inputs):
|
122
|
+
await tg.start_soon(bounded_fetch, semaphore, i, inp)
|
123
|
+
except BaseException as e:
|
124
|
+
# Re-raise the first exception directly instead of ExceptionGroup
|
125
|
+
if hasattr(e, "exceptions") and e.exceptions:
|
126
|
+
raise e.exceptions[0]
|
127
|
+
else:
|
128
|
+
raise
|
129
|
+
|
130
|
+
return results # type: ignore
|
131
|
+
|
132
|
+
|
133
|
+
async def retry_with_timeout(
|
134
|
+
func: Callable[[], Awaitable[T]],
|
135
|
+
max_retries: int = 3,
|
136
|
+
timeout: float = 30.0,
|
137
|
+
backoff_factor: float = 1.0,
|
138
|
+
) -> T:
|
139
|
+
"""Retry an async function with exponential backoff and timeout.
|
140
|
+
|
141
|
+
Args:
|
142
|
+
func: The async function to retry
|
143
|
+
max_retries: Maximum number of retries
|
144
|
+
timeout: Timeout for each attempt
|
145
|
+
backoff_factor: Multiplier for exponential backoff
|
146
|
+
|
147
|
+
Returns:
|
148
|
+
The result of the successful function call
|
149
|
+
|
150
|
+
Raises:
|
151
|
+
Exception: The last exception raised by the function
|
152
|
+
"""
|
153
|
+
last_exception = None
|
154
|
+
|
155
|
+
for attempt in range(max_retries):
|
156
|
+
try:
|
157
|
+
with move_on_after(timeout) as cancel_scope:
|
158
|
+
result = await func()
|
159
|
+
if not cancel_scope.cancelled_caught:
|
160
|
+
return result
|
161
|
+
else:
|
162
|
+
raise TimeoutError(f"Function timed out after {timeout}s")
|
163
|
+
except Exception as e:
|
164
|
+
last_exception = e
|
165
|
+
if attempt < max_retries - 1:
|
166
|
+
delay = backoff_factor * (2**attempt)
|
167
|
+
await anyio.sleep(delay)
|
168
|
+
continue
|
169
|
+
|
170
|
+
if last_exception:
|
171
|
+
raise last_exception
|
172
|
+
else:
|
173
|
+
raise RuntimeError("Retry failed without capturing exception")
|
174
|
+
|
175
|
+
|
176
|
+
class WorkerPool:
|
177
|
+
"""A pool of worker tasks that process items from a queue."""
|
178
|
+
|
179
|
+
def __init__(
|
180
|
+
self, num_workers: int, worker_func: Callable[[Any], Awaitable[None]]
|
181
|
+
):
|
182
|
+
"""Initialize a new worker pool."""
|
183
|
+
if num_workers < 1:
|
184
|
+
raise ValueError("num_workers must be >= 1")
|
185
|
+
if not callable(worker_func):
|
186
|
+
raise ValueError("worker_func must be callable")
|
187
|
+
|
188
|
+
self._num_workers = num_workers
|
189
|
+
self._worker_func = worker_func
|
190
|
+
self._queue = anyio.create_memory_object_stream(1000)
|
191
|
+
self._task_group = None
|
192
|
+
|
193
|
+
track_resource(self, f"WorkerPool-{id(self)}", "WorkerPool")
|
194
|
+
|
195
|
+
def __del__(self):
|
196
|
+
"""Clean up resource tracking."""
|
197
|
+
try:
|
198
|
+
untrack_resource(self)
|
199
|
+
except Exception:
|
200
|
+
pass
|
201
|
+
|
202
|
+
async def start(self) -> None:
|
203
|
+
"""Start the worker pool."""
|
204
|
+
if self._task_group is not None:
|
205
|
+
raise RuntimeError("Worker pool is already started")
|
206
|
+
|
207
|
+
self._task_group = create_task_group()
|
208
|
+
await self._task_group.__aenter__()
|
209
|
+
|
210
|
+
# Start worker tasks
|
211
|
+
for i in range(self._num_workers):
|
212
|
+
await self._task_group.start_soon(self._worker_loop)
|
213
|
+
|
214
|
+
async def stop(self) -> None:
|
215
|
+
"""Stop the worker pool."""
|
216
|
+
if self._task_group is None:
|
217
|
+
return
|
218
|
+
|
219
|
+
# Close the queue to signal workers to stop
|
220
|
+
await self._queue[0].aclose()
|
221
|
+
|
222
|
+
# Wait for all workers to finish
|
223
|
+
try:
|
224
|
+
await self._task_group.__aexit__(None, None, None)
|
225
|
+
finally:
|
226
|
+
self._task_group = None
|
227
|
+
|
228
|
+
async def submit(self, item: Any) -> None:
|
229
|
+
"""Submit an item for processing."""
|
230
|
+
if self._task_group is None:
|
231
|
+
raise RuntimeError("Worker pool is not started")
|
232
|
+
await self._queue[0].send(item)
|
233
|
+
|
234
|
+
async def _worker_loop(self) -> None:
|
235
|
+
"""Main loop for worker tasks."""
|
236
|
+
try:
|
237
|
+
async with self._queue[1]:
|
238
|
+
async for item in self._queue[1]:
|
239
|
+
try:
|
240
|
+
await self._worker_func(item)
|
241
|
+
except Exception as e:
|
242
|
+
logger.error(f"Worker error processing item: {e}")
|
243
|
+
except anyio.ClosedResourceError:
|
244
|
+
# Queue was closed, worker should exit gracefully
|
245
|
+
pass
|
246
|
+
|
247
|
+
async def __aenter__(self) -> WorkerPool:
|
248
|
+
"""Enter the worker pool context."""
|
249
|
+
await self.start()
|
250
|
+
return self
|
251
|
+
|
252
|
+
async def __aexit__(
|
253
|
+
self,
|
254
|
+
exc_type: type[BaseException] | None,
|
255
|
+
exc_val: BaseException | None,
|
256
|
+
exc_tb: TracebackType | None,
|
257
|
+
) -> None:
|
258
|
+
"""Exit the worker pool context."""
|
259
|
+
await self.stop()
|