C3 AI Documentation Home

Understanding Action Engines

This topic covers advanced concepts in the C3 Agentic AI Platform and may not be relevant to most users. This section assumes you are already familiar with C3 AI methods and how to implement the methods.

Action engines

In the C3 AI Type System, Action.Engines are responsible for compiling and invoking C3 AI methods. They also create the environments where these methods are compiled and executed. These environments must satisfy the Action.Requirements declared for the methods being executed, specifically when Action.Engine#canRun(Action.Requirement) returns true.

An Action.Engine includes all properties of an Action.Requirement, but it requires all optional properties of Action.Requirement. Both Action.Engine and Action.Requirement follow the same naming conventions.

Every Action.Engine has the following attributes:

  • A ImplLanguage.Runtime runtime, such as py, py-data, or py-tf.
  • A ImplLanguage.Executor executor, like jep, py4j, or ipython.
  • A ImplLanguage.RuntimeLocation location, such as client or server.

Resolving a runtime

An Action Engine executes actions in a resolved runtime. Each resolved runtime is created from its corresponding Action Engine. A resolved runtime includes:

  • The libraries of the engine's runtime, which also includes the libraries of the runtime's parents.
  • The libraries of the engine's executor, with the runtime named {{language}}-{{executor}}, such as py-jep.
  • The libraries of the engine's location, configured per language, with the runtime named {{language}}-{{location}}, like js-client.
  • Test libraries, which are only included if the app is in test mode, with the runtime named {{language}}-testing, such as py-testing.

An Action Engine may also include additional libraries in a resolved runtime to enhance system performance.

When a user resolves or upserts a runtime using ImplLanguage#resolveRuntime or ImplLanguage#upsertRuntime, at least one resolved runtime is created for each valid engine for the user-declared runtime. An engine is valid if its language supports the specified executor (see ImplLanguage#executors) and if the engine's executor supports the specified location (see ImplLanguage.Executor#locations). If any library conflicts arise during runtime resolution, the corresponding engine is considered invalid.

Staying in engine

When you call a method in C3 AI, it can either execute in the same action engine from which it was called or in a different action engine. If the method executes in the same context, it is referred to as staying in engine.

Staying in engine is desirable for both performance and functionality:

  • Performance:
    • If the method does not stay in engine, all arguments must be transferred from the calling context to the executing context, and the return value must be transferred back. This data transfer incurs overhead, sometimes significantly.
    • If the method does not stay in engine, another engine must execute the action, creating or retrieving an engine, which also incurs overhead.
  • Functionality:
    • The C3 AI server cannot pass native values (e.g., an sklearn pipeline instance) across engines. Therefore, methods that accept or return such native values must stay in engine to function properly.

A method stays in engine if the action engine of the current context can run the action requirements specified for the method. The section below explains what it means for an action engine to be capable of running an action requirement.

When can an engine run an action

All code in the C3 AI ecosystem runs in some Action.Engine. You can introspect the current Action.Engine using the following code snippet:

Python
c3.Context.actionEngine

To determine whether your method executes locally (within the same engine) or remote to a different engine, the C3 AI server evaluates the following expression:

Python
c3.<TypeName>.meta().method(<method name>).shouldStayInEngine(c3.Context.actionEngine)

If this expression returns true, the method executes within the current action engine and is not be dispatched to a different engine (it will not remote).

Method#shouldStayInEngine returns true if any of the method's Action.Requirements (a method may have multiple, such as js | py) can be run by the provided Action.Engine. An Action.Engine can run an Action.Requirement (see Action.Engine#canRun) if:

  • The Action.Engine's runtime can run the Action.Requirement's runtime (for example, py, py-data, py-data-science).
  • The Action.Engine's executor can run the Action.Requirement's executor, or if the Action.Requirement does not specify an executor (e.g., jep, py4j, ipython).
  • The Action.Engine's location can run the Action.Requirement's location, or if the Action.Requirement does not specify a location (e.g., client, server).

Runtime compatibility

To determine if an Action.Engine's runtime can run an Action.Requirement's runtime, C3 AI uses ImplLanguage#canRun. This is determined based on runtime inheritance. Runtimes can specify libraries and parents. The runtime satisfies the libraries it specifies and the union of all libraries from its parents (and ancestors). Consequently, a descendant runtime can always run an ancestor runtime. runtimeA.canRun(runtimeB) returns true if any of the following are true:

  • runtimeA and runtimeB are the same.
  • runtimeA.isDescendantOf(runtimeB).
  • runtimeB does not directly declare any libraries, and runtimeA can run all of runtimeB's parents (for example, if runtimeB declares no libraries and has one parent py-foo, and runtimeA has parent py-foo, then runtimeA can run runtimeB).

Note: Since descendant runtimes can run ancestor runtimes, you may observe that an engine's runtime is not the same as the runtime claimed by the method the engine is executing. For example, your method claimed py-data may run in an engine with runtime py-data-science.

Executor and location compatibility

The checks for executor and location compatibility are simple equivalence checks. If an Action.Requirement does not specify an executor or location, it is considered compatible with all executors and locations. Otherwise, the engine's location or executor must exactly match the requirement's location or executor.

Example

Consider the following method:

Type
foo: function(value: string): int js | py-data-client

Assume you are working within a Jupyter notebook that uses a kernel named py-data-science``. In this environment, the action engine is configured with the ipython executor and operates in the client location. As a result, your action engine is identified as py-data-science-client-ipython`.

When you invoke the foo method, the key question is whether it executes within the current action engine or if it is remoted to a different engine.

  • Your current Action.Engine is py-data-science-client-ipython because you are in a Jupyter notebook with a kernel named py-data-science. This engine uses the ipython executor and operates in the client location.
  • The method foo has two Action.Requirements: js and py-data-client.
    • Your engine does not satisfy the js requirement since it’s a different language (py-data-science vs. js).
    • However, the py-data-science runtime can run the py-data runtime because py-data-science is a descendant of py-data.
    • Your engine is also in the client location, which matches the client location required by foo.
    • Finally, since the py-data-client requirement does not specify an executor, your engine's ipython executor is acceptable.

When you call foo from your engine, the method does not remote; it executes within the same engine (stay in the engine).

Caveats

If the current Action.Engine can run the method being called within it, it will. However, there are some exceptions:

  • Methods can have annotations specifying target nodes, threads, or apps for execution. Such methods will remote to the correct node, thread, or app if necessary.
    • Refer to the fields on Ann.Call for more information.
  • Engine instances with a dedicated thread pool will have implicit Ann.Call#threadPool annotations for their member methods, dispatching those methods to their dedicated thread pool.
  • If a method is called server-side and there is both a java implementation and an implementation the current Action.Engine can run, the java implementation will be called.
    • Users can modify this behavior with the Ann.Call#stayInEngine annotation.
Was this page helpful?