Skip to content

chatomon.connectors.adk

Build an atom into a native Google ADK application.

Install with pip install 'chatomon[adk]'.

Classes:

Name Description
ADKConnector

Builds an atom into a native google.adk.apps.App.

ADKConnector

ADKConnector(
    model: str | BaseLlm,
    *,
    resolve_model: (
        Callable[[str], BaseLlm | str] | None
    ) = None,
    app_kwargs: Mapping[str, Any] | None = None,
    **agent_kwargs: Any
)

Bases: Connector['App']

Builds an atom into a native google.adk.apps.App.

The app's root agent is the nucleus, an LlmAgent, and its first plugin is chatomon's; your own plugins come after it. Run the app with ADK's own Runner(app=app, ...), adk web or adk run: the agent refuses to run outside it. The id of the ADK session is the conversation id.

When the atom has output orbitals the nucleus is called without streaming, so a caller that asks for streaming receives each reply whole, once it is checked. Live runs are refused.

Parameters:

Name Type Description Default
model str | BaseLlm

The nucleus model: an ADK BaseLlm or a name ADK's registry resolves.

required
resolve_model Callable[[str], BaseLlm | str] | None

Maps the model id of a JudgeOrbital to an ADK model or to a model name; by default the id is a name for the registry.

None
app_kwargs Mapping[str, Any] | None

Passed to google.adk.apps.App (plugins, context_cache_config, resumability_config, ...).

None
**agent_kwargs Any

Passed to google.adk.agents.LlmAgent (tools, generate_content_config, callbacks, ...).

{}

Raises:

Type Description
ChatomonConfigError

If an argument is set by chatomon from the atom (name, instruction; name and root_agent of the app), cannot be checked by the orbitals (sub_agents, a mode other than "chat", output_schema, static_instruction, global_instruction), or is an events compaction without a summarizer.

Examples:

import asyncio

from google.adk.runners import InMemoryRunner

from chatomon import Atom
from chatomon.connectors.adk import ADKConnector


async def main() -> None:
    app = Atom(
        name="speedypack",
        instructions="You are the customer assistant of SpeedyPack.",
        connector=ADKConnector("gemini-3.5-flash"),
    ).build()
    runner = InMemoryRunner(app=app)
    events = await runner.run_debug("Hello", quiet=True)
    print(events[-1].content.parts[0].text)


asyncio.run(main())

Methods:

Name Description
build

Build the native app with chatomon's plugin and the nucleus as its root agent.

generate_structured

Make one structured call straight to an ADK model, with no agent or session.

Source code in src/chatomon/connectors/adk.py
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
def __init__(
    self,
    model: str | BaseLlm,
    *,
    resolve_model: Callable[[str], BaseLlm | str] | None = None,
    app_kwargs: Mapping[str, Any] | None = None,
    **agent_kwargs: Any,
) -> None:
    app_kwargs = dict(app_kwargs or {})
    taken = sorted(_FROM_THE_ATOM & agent_kwargs.keys())
    taken += [f"app_kwargs[{key!r}]" for key in sorted(_APP_FROM_THE_ATOM & app_kwargs.keys())]
    if taken:
        raise ChatomonConfigError(
            f"{', '.join(taken)} is set by chatomon from the atom; "
            "remove it from ADKConnector(...)"
        )
    for key, reason in _REFUSED.items():
        if agent_kwargs.get(key) and (key, agent_kwargs[key]) != ("mode", "chat"):
            raise ChatomonConfigError(f"ADKConnector does not take {key}: {reason}")
    compaction = app_kwargs.get("events_compaction_config")
    if compaction is not None and getattr(compaction, "summarizer", None) is None:
        raise ChatomonConfigError(_NO_SUMMARIZER)
    self.model = model
    self.resolve_model = resolve_model
    self.app_kwargs = app_kwargs
    self.agent_kwargs = agent_kwargs

build

build(atom: Atom[App], engine: Engine) -> App

Build the native app with chatomon's plugin and the nucleus as its root agent.

Parameters:

Name Type Description Default
atom Atom[App]

The validated atom.

required
engine Engine

The engine that runs the atom's orbitals.

required

Returns:

Type Description
App

A google.adk.apps.App named like the atom.

Raises:

Type Description
ChatomonConfigError

If the atom's name is not a valid ADK agent name, or ADK's registry does not know the model's name.

Source code in src/chatomon/connectors/adk.py
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
def build(self, atom: Atom[App], engine: Engine) -> App:
    """Build the native app with chatomon's plugin and the nucleus as its root agent.

    Args:
        atom: The validated atom.
        engine: The engine that runs the atom's orbitals.

    Returns:
        A `google.adk.apps.App` named like the atom.

    Raises:
        ChatomonConfigError: If the atom's name is not a valid ADK agent
            name, or ADK's registry does not know the model's name.
    """
    if not atom.name.isidentifier() or atom.name == "user":
        raise ChatomonConfigError(_BAD_NAME.format(name=atom.name))
    inner = _resolve(self.model)
    nucleus = _Nucleus(model=inner.model, inner=inner, engine=engine)
    prompt = atom.system_prompt
    app_kwargs = dict(self.app_kwargs)
    plugins = [
        _ChatomonPlugin(engine, atom.blocked_placeholder, nucleus),
        *(app_kwargs.pop("plugins", None) or ()),
    ]
    agent = LlmAgent(
        name=atom.name,
        model=nucleus,
        # A function, so that ADK does not read braces in the text as session variables.
        instruction=lambda _context: prompt,
        **self.agent_kwargs,
    )
    return App(name=atom.name, root_agent=agent, plugins=plugins, **app_kwargs)

generate_structured async

generate_structured(
    model: str,
    *,
    system: str,
    prompt: str,
    schema: type[ModelT]
) -> ModelT

Make one structured call straight to an ADK model, with no agent or session.

Parameters:

Name Type Description Default
model str

Model id; passed through resolve_model when set.

required
system str

System instruction.

required
prompt str

User prompt.

required
schema type[ModelT]

Output type.

required

Returns:

Type Description
ModelT

The validated output.

Raises:

Type Description
ChatomonConfigError

If the model id resolves to no ADK model.

ValueError

If the model returned an error or no text, or text that does not match schema.

Source code in src/chatomon/connectors/adk.py
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
async def generate_structured(
    self, model: str, *, system: str, prompt: str, schema: type[ModelT]
) -> ModelT:
    """Make one structured call straight to an ADK model, with no agent or session.

    Args:
        model: Model id; passed through `resolve_model` when set.
        system: System instruction.
        prompt: User prompt.
        schema: Output type.

    Returns:
        The validated output.

    Raises:
        ChatomonConfigError: If the model id resolves to no ADK model.
        ValueError: If the model returned an error or no text, or text that
            does not match `schema`.
    """  # noqa: DOC502 - the first is raised by _resolve(), the second also by pydantic
    resolved = self.resolve_model(model) if self.resolve_model is not None else model
    llm = _resolve(resolved)
    request = LlmRequest(
        model=llm.model,
        contents=[types.Content(role="user", parts=[types.Part(text=prompt)])],
        config=types.GenerateContentConfig(system_instruction=system),
    )
    request.set_output_schema(schema)
    async with aclosing(llm.generate_content_async(request, stream=False)) as responses:
        answers = [response async for response in responses]
    failed = not answers or answers[-1].error_code is not None
    text = "" if failed else _text(answers[-1].content)
    if not text:
        raise ValueError(_NO_ANSWER)
    return schema.model_validate_json(text)