Skip to content

Relax event config requirements and add embedding call helper#58

Merged
JohnRichard4096 merged 5 commits intomainfrom
feat/non-config-event
Apr 25, 2026
Merged

Relax event config requirements and add embedding call helper#58
JohnRichard4096 merged 5 commits intomainfrom
feat/non-config-event

Conversation

@JohnRichard4096
Copy link
Copy Markdown
Member

@JohnRichard4096 JohnRichard4096 commented Apr 25, 2026

Summary by Sourcery

Relax configuration requirements for event triggering and enhance model interaction utilities.

New Features:

  • Add an embedding call helper that proxies to the model adapter with reflection and configuration support.

Bug Fixes:

  • Allow events to be triggered without requiring a config instance in the argument list.
  • Fix cancellation handling by only treating CancelException as a hard stop while leaving other matcher exceptions to propagate normally.

Enhancements:

  • Refine dependency resolution and event matcher invocation signatures to use keyword-only parameters and optional configuration.
  • Simplify adapter reflection helper to accept a typed adapter callback and propagate extra call parameters directly to adapter methods.
  • Expose agent strategy classes in the public module exports for easier external use.
  • Clean up unused exceptions and placeholder classes in matcher and MCP tooling modules.

Build:

  • Bump library version to 0.8.4 to reflect the updated event handling and model utilities.

Tests:

  • Remove obsolete reflection helper test that depended on the old variable-args adapter calling pattern.

@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai Bot commented Apr 25, 2026

Reviewer's Guide

Refactors event handling and dependency resolution APIs to support optional configuration and improved type clarity, aligns agent strategy abstractions, centralizes adapter reflection usage while adding embedding support, removes unused exceptions/utilities, and bumps the package version to 0.8.4.

Sequence diagram for updated trigger_event and matcher execution

sequenceDiagram
    participant Caller
    participant Matcher as Matcher
    participant EventRegistry as EventRegistry
    participant Handler as HandlerFunction

    Caller->>Matcher: trigger_event(event, *args, config=None, exception_ignored, **kwargs)
    activate Matcher
    Matcher->>Matcher: extract event and optional config from args/kwargs
    Matcher->>EventRegistry: get_handlers(event_type)
    EventRegistry-->>Matcher: handlers_by_priority

    loop priorities
        Matcher->>Matcher: _simple_run(matcher_list, event, exception_ignored, extra_args, extra_kwargs, config)
        activate Matcher
        loop matcher_list
            Matcher->>Matcher: build session_args [matcher, event, *extra_args, config?]
            Matcher->>Matcher: build session_kwargs from extra_kwargs
            alt has runtime_args or runtime_kwargs
                Matcher->>Matcher: _do_runtime_resolve(runtime_args, runtime_kwargs, args2update, kwargs2update, session_args, session_kwargs, exception_ignored)
                alt resolve failed
                    Matcher-->>Matcher: raise RuntimeError
                end
            end
            alt has Depends in handler kwargs
                Matcher->>Matcher: _do_runtime_resolve(runtime_args={}, runtime_kwargs=d_kw, args2update=[], kwargs2update=f_kwargs, session_args, session_kwargs, exception_ignored)
                alt resolve failed
                    Matcher-->>Matcher: continue to next matcher
                end
            end
            Matcher->>Handler: await handler(*session_args, **session_kwargs)
            alt handler raises CancelException
                Matcher-->>Caller: return False (stop processing)
            else handler raises ChatException or other
                Matcher-->>Matcher: log and continue based on exception type
            end
        end
        deactivate Matcher
    end
    Matcher-->>Caller: return None
Loading

Sequence diagram for shared adapter reflection in libchat

sequenceDiagram
    participant Client
    participant Libchat as libchat
    participant PresetManager
    participant AdapterClass as ModelAdapterClass
    participant Adapter as ModelAdapter

    Client->>Libchat: tools_caller(messages, tools, tool_choice, preset?, config?)
    activate Libchat
    Libchat->>PresetManager: get_default_preset() (if preset is None)
    PresetManager-->>Libchat: preset
    Libchat->>Libchat: _call_with_reflection(preset, _call_tools, config)
    activate Libchat
    Libchat->>AdapterClass: instantiate(preset, config)
    AdapterClass-->>Libchat: Adapter
    Libchat->>Adapter: call_tools(messages, tools, tool_choice)
    Adapter-->>Libchat: tools_result
    Libchat-->>Client: tools_result
    deactivate Libchat

    Client->>Libchat: call_completion(messages, preset?, config?, **kwargs)
    activate Libchat
    Libchat->>PresetManager: get_default_preset() (if preset is None)
    PresetManager-->>Libchat: preset
    Libchat->>Libchat: _call_with_reflection(preset, _call_api, config)
    activate Libchat
    Libchat->>AdapterClass: instantiate(preset, config)
    AdapterClass-->>Libchat: Adapter
    Libchat->>Adapter: call_api(messages_model_dump, **kwargs)
    Adapter-->>Libchat: async_generator
    Libchat-->>Client: async_generator
    deactivate Libchat

    Client->>Libchat: call_embedding(text_iterable, preset, config?, **kwargs)
    activate Libchat
    Libchat->>Libchat: _call_with_reflection(preset, _call_embed, config)
    activate Libchat
    Libchat->>AdapterClass: instantiate(preset, config)
    AdapterClass-->>Libchat: Adapter
    Libchat->>Adapter: call_embed(text_iterable, **kwargs)
    Adapter-->>Libchat: embeddings
    Libchat-->>Client: embeddings
    deactivate Libchat
Loading

Class diagram for matcher, exceptions, and event handling changes

classDiagram
    class MatcherException {
    }

    class CancelException {
    }

    MatcherException <|-- CancelException

    class Matcher {
        <<utility>>
        +async _do_runtime_resolve(runtime_args: dict~int, DependsFactory~, runtime_kwargs: dict~str, DependsFactory~, args2update: list~Any~, kwargs2update: dict~str, Any~, session_args: list~Any~, session_kwargs: dict~str, Any~, exception_ignored: tuple~type~BaseException~~, ...~) bool
        +async _simple_run(matcher_list: list~FunctionData~, event: BaseEvent, /, exception_ignored: tuple~type~BaseException~~, extra_args: tuple, extra_kwargs: dict~str, Any~, config: AmritaConfig|None = None) bool
        +async trigger_event(event: BaseEvent, *args: Any, config: AmritaConfig|None = None, exception_ignored: tuple~type~Exception~~ = (), **kwargs) None
    }

    class FunctionData {
        +signature: Signature
        +frame: FrameType
        +matcher
        +function
        +d_args
        +d_kwargs
    }

    class BaseEvent {
        +get_event_type() EventTypeEnum|str
    }

    class EventRegistry {
        +get_handlers(event_type: EventTypeEnum|str) dict~int, list~FunctionData~~
    }

    class AmritaConfig {
    }

    class DependsFactory {
    }

    class EventTypeEnum {
    }

    Matcher --> EventRegistry : uses
    Matcher --> BaseEvent : handles
    Matcher --> FunctionData : executes
    Matcher --> AmritaConfig : optional config
    Matcher --> DependsFactory : resolves
    BaseEvent --> EventTypeEnum : returns

    class CancelExceptionHandler {
        +handle(e: CancelException)
    }

    CancelExceptionHandler ..> CancelException : handles
Loading

Class diagram for updated agent strategy abstractions

classDiagram
    class BaseReActAgentStrategy {
        <<abstract>>
        +ctx
        +async _build_stop_response_and_append()
        +async _handle_error_append()
        +async _append_reasoning(tool_call: ToolCall, reasoning_content: UniResponse~str, None~)
        +async _append_tool_result_to_context(tool_call: ToolCall, func_response: str, response_msg: UniResponse~None, list~ToolCall~|None~)
    }

    class ReActAgentStrategy {
    }

    class HybridReActAgentStrategy {
    }

    class NoActionAgentStrategy {
    }

    class AmritaAgentStrategy {
        <<alias of ReActAgentStrategy>>
    }

    class ToolCall {
    }

    class UniResponse~T, E~ {
    }

    BaseReActAgentStrategy <|-- ReActAgentStrategy
    BaseReActAgentStrategy <|-- HybridReActAgentStrategy
    BaseReActAgentStrategy <|-- NoActionAgentStrategy

    BaseReActAgentStrategy --> ToolCall : uses
    BaseReActAgentStrategy --> UniResponse : uses

    AmritaAgentStrategy .. ReActAgentStrategy : alias
Loading

Class diagram for shared adapter reflection and new embedding support

classDiagram
    class ModelPreset {
        +base_url: str
        +model: str
        +config
    }

    class AmritaConfig {
    }

    class PresetManager {
        +get_default_preset() ModelPreset
    }

    class ModelAdapter {
        +ModelAdapter(preset: ModelPreset, config: AmritaConfig)
        +call_tools(messages: CONTENT_LIST_TYPE, tools, tool_choice)
        +call_api(messages: list~dict~, **kwargs) AsyncGenerator
        +call_embed(text: Iterable~str~, **kwargs) Sequence~EmbeddingChunk~
    }

    class EmbeddingChunk {
    }

    class LibchatAPI {
        <<module functions>>
        +async tools_caller(messages: CONTENT_LIST_TYPE, tools, tool_choice, preset: ModelPreset|None = None, config: AmritaConfig|None = None)
        +async call_completion(messages: CONTENT_LIST_TYPE, preset: ModelPreset|None = None, config: AmritaConfig|None = None, **kwargs) AsyncGenerator
        +async call_embedding(text: Iterable~str~, preset: ModelPreset, config: AmritaConfig|None = None, **kwargs) Sequence~EmbeddingChunk~
        +async _call_with_reflection(preset: ModelPreset, call_func: Callable~ModelAdapter, Awaitable~T~~, config: AmritaConfig) T
    }

    LibchatAPI --> PresetManager : uses
    LibchatAPI --> ModelPreset : uses
    LibchatAPI --> AmritaConfig : uses
    LibchatAPI --> ModelAdapter : instantiates
    LibchatAPI --> EmbeddingChunk : returns
    ModelAdapter --> ModelPreset : configured by
    ModelAdapter --> AmritaConfig : configured by
    ModelAdapter --> EmbeddingChunk : produces embeddings
Loading

File-Level Changes

Change Details Files
Adjust matcher runtime dependency resolution and event triggering interfaces to support optional config and stricter typing.
  • Make _do_runtime_resolve keyword-only to reduce call-site ambiguity and improve readability.
  • Update _simple_run to accept config as an optional keyword-only parameter, adjust session argument construction so config is appended only when provided, and add explicit type annotations for signature, frame, file name, and line number.
  • Switch all _do_runtime_resolve invocations in _simple_run to keyword-based argument passing for better clarity and safety.
  • Simplify CancelException handling by removing BlockException from isinstance checks and dropping BlockException definition entirely.
  • Relax trigger_event to accept optional config via keyword-only argument, remove the hard requirement that config be present in *args, and update calls to _simple_run accordingly.
src/amrita_core/hook/matcher.py
src/amrita_core/hook/exception.py
Realign agent abstractions around reasoning vs tool-result handling and expand public exports for agent strategies.
  • Swap the responsibilities and docstrings of _append_reasoning and _append_tool_result_to_context so that _append_reasoning handles reasoning content and _append_tool_result_to_context handles tool execution results.
  • Ensure abstract method signatures and documentation are consistent with their intended usage across agent strategies.
  • Export all key agent strategy types (AmritaAgentStrategy alias and concrete strategy classes) via all for backward compatibility and discoverability.
src/amrita_core/builtins/agent.py
Constrain adapter reflection helper to a single, well-typed calling convention, propagate kwargs support, and add embedding API.
  • Change _call_with_reflection to accept a call_func that only takes a ModelAdapter and returns an awaitable, removing passthrough *args/**kwargs to simplify and harden the helper’s signature.
  • Update tools_caller to use the new _call_with_reflection signature via an inner _call_tools that captures messages, tools, and tool_choice via closure.
  • Update call_completion to accept **kwargs and forward them to adapter.call_api, while adapting to the new _call_with_reflection contract.
  • Introduce call_embedding, which uses _call_with_reflection to invoke adapter.call_embed with optional **kwargs and returns a sequence of EmbeddingChunk.
  • Remove an outdated unit test that relied on the old _call_with_reflection *args/**kwargs behavior.
src/amrita_core/libchat.py
tests/test_libchat.py
Remove unused or redundant utility types from tools module.
  • Delete NOT_GIVEN placeholder type from MCP tools module as it is no longer used.
src/amrita_core/tools/mcp.py
Bump library version to reflect API and behavior changes.
  • Update project version from 0.8.3.1 to 0.8.4 in pyproject.toml.
pyproject.toml

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues, and left some high level feedback:

  • In _simple_run, session_args now append config after *extra_args instead of before, which can break existing handler signatures and dependency index assumptions; consider preserving the original argument order (matcher, event, config, *extra_args) or explicitly updating all dependent call sites/signatures accordingly.
  • The new trigger_event API both accepts config as a keyword argument and also infers it from *args, which makes the call contract harder to reason about; consider making config strictly keyword-only (and not scanning *args for AmritaConfig) to keep invocation clearer and less error-prone.
  • With _call_with_reflection now forcing call_func to accept only adapter, more complex uses must create nested wrappers as seen in tools_caller and call_completion; if you expect richer usage patterns, consider allowing *args/**kwargs again or providing a small helper for constructing these adapter-bound callables to avoid repetitive wrapper boilerplate.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `_simple_run`, `session_args` now append `config` after `*extra_args` instead of before, which can break existing handler signatures and dependency index assumptions; consider preserving the original argument order (matcher, event, config, *extra_args) or explicitly updating all dependent call sites/signatures accordingly.
- The new `trigger_event` API both accepts `config` as a keyword argument and also infers it from `*args`, which makes the call contract harder to reason about; consider making `config` strictly keyword-only (and not scanning `*args` for `AmritaConfig`) to keep invocation clearer and less error-prone.
- With `_call_with_reflection` now forcing `call_func` to accept only `adapter`, more complex uses must create nested wrappers as seen in `tools_caller` and `call_completion`; if you expect richer usage patterns, consider allowing `*args`/`**kwargs` again or providing a small helper for constructing these adapter-bound callables to avoid repetitive wrapper boilerplate.

## Individual Comments

### Comment 1
<location path="src/amrita_core/hook/matcher.py" line_range="417-420" />
<code_context>
                 )
                 continue
             except Exception as e:
-                if isinstance(e, CancelException | BlockException):
+                if isinstance(e, CancelException):
                     logger.info("Cancelled Matcher processing")
                     return False
</code_context>
<issue_to_address>
**issue (bug_risk):** Dropping `BlockException` changes control-flow semantics and may break existing code relying on that type.

With `BlockException` removed from the control-flow path and the `isinstance` check restricted to `CancelException`, any external code that raises or catches `BlockException` may now break or behave differently. If the goal is to retire `BlockException`, consider a compatibility alias (e.g., `BlockException = CancelException`) or a deprecation path to avoid breaking downstream users.
</issue_to_address>

### Comment 2
<location path="src/amrita_core/libchat.py" line_range="305-310" />
<code_context>
     return resp
+
+
+async def call_embedding(
+    text: Iterable[str],
+    preset: ModelPreset,
+    config: AmritaConfig | None = None,
+    **kwargs,
+) -> Sequence[EmbeddingChunk]:
+    config = config or get_config()
+
</code_context>
<issue_to_address>
**suggestion:** `call_embedding` requires an explicit preset unlike `call_completion` and `tools_caller`, which may be inconsistent for users.

Unlike `tools_caller` and `call_completion`, which accept an optional `preset` and default to `PresetManager().get_default_preset()`, this function requires a `ModelPreset`. Unless there’s a strong reason to force an explicit preset, consider making it optional and using the same defaulting behavior for consistency.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src/amrita_core/hook/matcher.py
Comment thread src/amrita_core/libchat.py
@JohnRichard4096
Copy link
Copy Markdown
Member Author

@sourcery-ai title

@sourcery-ai sourcery-ai Bot changed the title Enhance event handling and update library version Relax event config requirements and add embedding call helper Apr 25, 2026
@JohnRichard4096 JohnRichard4096 merged commit e9421a0 into main Apr 25, 2026
3 checks passed
@JohnRichard4096 JohnRichard4096 deleted the feat/non-config-event branch April 25, 2026 12:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant