Implementing Type Methods
If you are not familiar with the concept of C3 AI Methods and how they are declared, it is recommended that you first read the documentation on Defining Methods in the C3 AI Type System.
Claiming methods with Action.Requirements
Implementing a method in the Type System requires you claim the method with an Action.Requirement. An Action.Requirement informs the C3 AI server what requirements must be satisfied by the environment executing your method implementation.
An Action.Requirement allows you to specify:
A Method is declared by putting the name of your Action.Requirement at the end of your method declaration.
For example,
method1: function(arg: string): int js
method2: function(arg: string): int py-data-science
method3: function(arg: string): int js-server
method4: function(arg: string): int py-foo-client
method4: function(arg: string): int js-ide-server-nodeIn the above method declarations, js, py-data-science, js-server, py-foo-client, and js-ide-server-node are all examples of Action.Requirement claims.
Runtime
A ImplLanguage.Runtime runtime is the only mandatory property of a Action.Requirement. This property allows you to specify what language and libraries are necessary for executing your method. You can use an existing runtime, or create you own. For more information on how to make your own runtime, refer to Making a Runtime.
When you specify a runtime for an Action.Requirement, your method implementation executes in a runtime that can run the specified runtime in your Action.Requirement. Refer to {@Link ImplLanguage.Runtime#canRun} for more details.
An example of a runtime in the code snippet below is js or py:
calculateDepreciation: member function(purchasePrice: float, currentYear: int): float js | pyThis method must be implemented in JavaScript and Python and returns an float.
Location
location is an optional property for an Action.Requirement. Valid locations are client and server, and their semantics are described below:
client: This is a process from which users interface with the C3 AI server from. The C3 AI server cannot dispatch actions to this process.server: This is a process which the C3 AI server dispatches actions to. This is a process which only the C3 AI server (not the user) interfaces with.
If you specify a location, you are promised your method only executes in that location. If this is not possible (for example, if a method claimed with the client location is called from within the C3 AI server), a runtime exception occurs.
Not specifying a location implies your method may run in either location.
Executor
An executor is another optional property for an Action.Requirement. This property is not meaningful for most users, and is usually only specified by power users.
Each ImplLanguage can have one or more executors. An executor is responsible for executing a method. Different executors have different advantages/disadvantages as it applies to performance and functionality. The C3 AI server selects the optimal executor for your Method, though an Action.Requirement allows you to configure this manually.
Not specifying any executor implies your method may run in any executor.
Naming your requirement
From the documentation above, you should have an understanding what properties you would like your Action.Requirement to have.
An Action.Requirement name is the concatenation of the name of its runtime name, location name, and executor name (in that order, with null values for location and executor excluded), separated by a -.
For example:
| Action.Requirement | runtime | location | executor |
|---|---|---|---|
js-ide-server-node | js-ide | server | node |
py-foo-client | py-foo | client | |
js-server | js | server | |
py-data-science | py-data-science | ||
js | js |
Some Action.Requirements above do not specify a location or executor.
Writing an implementation
After declaring the metadata necessary for the C3 AI server to find and execute your method, you must implement your method.
Adding an implementation file
Method implementations should live in the same directory as their corresponding *.c3typ files. These implementation files must have the name of the Type you are implementing methods for, with the file extension for the implementation language.
For example, suppose you declare the following Type in the path /foo/bar/Automobile.c3typ:
type Automobile {
pyFunc: function(a: int): int py
}The method Automobile#pyFunc can be implemented in file /foo/bar/Automobile.py:
def pyFunc(cls, a):
return a+1For scripting languages (JavaScript and Python), the implementation file must contain top-level functions by the same name as the C3 AI methods they implement. These functions can be compiled and invoked when the corresponding C3 AI method is called.
Implementing Python methods
Requirements and best practices for implementing C3 AI methods in Python is documented here.
Implementing JavaScript methods
Requirements and best practices for implementing C3 AI methods in JavaScript is documented here.
Action requirement file extensions
This section describes how C3 AI Type method implementations can be implemented in different namespaces. Separating implementations into different namespaces can prevent runtime compilation errors, and provide a safer and more performant alternative to runtime checks for if your current context satisfies particular requirements.
A file which contains method implementations for a Type can optionally specify an Action.Requirement. You are promised these files only be compiled in contexts where those Action.Requirements are satisfied. The Action.Requirement name is specified between the Type name and file extensions in the file name. For example, the file Automobile.py-data-science.py would be compiled only when compiling methods for Type Automobile in a runtime that ImplLanguage.Runtime#canRun can run py-data-science.
Use case: Safe imports
In the aforementioned Automobile.py-data-science.py file, you can import numpy as np in the top of Automobile.py-data-science.py. This is not safe to do in Automobile.py, which may be compiled in a runtime that does not have numpy, for instance.
Use case: Avoiding runtime conditionals
Suppose you have the following code in your method implementation file Automobile.py:
def myFunc(cls, arg):
if c3.Context.location == "server":
return doServerCode(arg)
elif c3.Context.location == "client":
return doClientCode(arg)Though minimal, evaluating the above conditionals adds overhead to your method. You can achieve the same functionality with improved performance with separate files Automobile.py-server.py:
def myFunc(cls, arg):
return doServerCode(arg)and file Automobile.py-client.py:
def myFunc(cls, arg):
return doClientCode(arg)Only one of the two files above can be compiled in a single context.
Compilation order
Multiple implementation files for a given language are compiled in the same namespace. Thus, one file can reference objects from another file. In general, it is safe for file A to reference objects in file B if A's Action.Requirement Action.Requirement#meetsRequirement meets B's Action.Requirement. Full documentation of compilation order is described by TypeMeta#sourceCodeAndFilePaths.
Multiple claims
The C3 Agentic AI Platform has support for implementations of type methods in multiple languages. This is particularly relevant for the Type System and basic utility types which must be local. Users can specify multiple claims by enumerating their various Action.Requirement's, separated by " | ". For example, the following method has 2 claims (js-ide, and py-data-science):
foo: function(value: string): int js-ide | py-data-scienceIn general, when there is more than one implementation to execute, C3 AI tends to select the implementation (if it exists) that can run in the current context. Otherwise, C3 AI tends to select the Java implementation (if it exists). These conditions do not hold true in all scenarios; there are many nuances that determine which implementation is used, and where it is executed. To learn more about the different implementations, refer to Understanding Action Engines.