Interact with a Service
We previously touched on how to use resource_handles to interact with a service. In this section we will dive deeper on how you can make your Skill communicate with a service.
Prerequisites
Before attempting this guide, you must:
- Deploy a solution
- Set up your development environment
- Connect VS Code to your cloud organization
- Know how to create skills
- Understand the Skill interface
Take a look at the "Calculator" service. This service performs basic calculator operations.
Let's create a Skill to add two numbers using this service.
Create a Skill with the following settings:
Skill:
- Language:
Python - Skill ID:
com.example.adder
Insert code into the Skill
The Skill will need to create a gRPC client to communicate with the calculator service.
Add the following imports to adder.py:
import grpc
from intrinsic.util.grpc import connection
from intrinsic.util.grpc import interceptor
from intrinsic.assets.services.examples.calcserver import calc_server_pb2
from intrinsic.assets.services.examples.calcserver import calc_server_pb2_grpc
Paste the following code as a top-level function inside adder.py:
_SERVICE_SLOT: str = "calculator"
def make_grpc_stub(resource_handle):
logging.info(f"Address: {resource_handle.connection_info.grpc.address}")
logging.info(f"Server Instance: {resource_handle.connection_info.grpc.server_instance}")
logging.info(f"Header: {resource_handle.connection_info.grpc.header}")
# Create a gRPC channel without using TLS
grpc_info = resource_handle.connection_info.grpc
grpc_channel = grpc.insecure_channel(grpc_info.address)
connection_params = connection.ConnectionParams(
grpc_info.address, grpc_info.server_instance, grpc_info.header
)
intercepted_channel = grpc.intercept_channel(
grpc_channel,
interceptor.HeaderAdderInterceptor(connection_params.headers),
)
return calc_server_pb2_grpc.CalculatorStub(intercepted_channel)
For a more detailed explanation, check out making a gRPC stub from a resource_handle
You also need to specify additional dependencies.
Add the following dependencies to the adder py_library targets in adder/BUILD:
"@ai_intrinsic_sdks//intrinsic/assets/services/examples/calcserver:calc_server_py_pb2",
"@ai_intrinsic_sdks//intrinsic/assets/services/examples/calcserver:calc_server_py_pb2_grpc",
"@ai_intrinsic_sdks//intrinsic/util/grpc:connection",
"@ai_intrinsic_sdks//intrinsic/util/grpc:interceptor",
The Skill will also need to declare that it requires the calculator service in its skill manifest.
See the service_proto_prefixes field from the calculator service manifest.
The value of that field must be put into the capability_names field of the dependency in the skill manifest.
Add the following dependency to adder/adder.manifest.textproto:
dependencies {
required_equipment {
key: "calculator"
value {
capability_names: "intrinsic_proto.services.Calculator"
}
}
}
A Skill must only connect to services that it was called with either as dependencies in its parameters or in its context. Circumventing this by hardcoding known Service addresses can lead to unexpected behavior. In particular, platform services must only be called via the provided interfaces. An example is described in accessing the belief world.
Finish the adder Skill
Add the following lines to adder/adder.proto:
message AdderParams {
int32 x = 1;
int32 y = 2;
}
The adder Skill must create a gRPC client to the calculator service, and then ask the calculator service to add two numbers.
Put the following code into the execute method inside adder/adder.py:
def execute(
self,
request: skill_interface.ExecuteRequest[adder_pb2.AdderParams],
context: skill_interface.ExecuteContext,
) -> None:
stub = make_grpc_stub(context.resource_handles[_SERVICE_SLOT])
response = stub.Calculate(
calc_server_pb2.CalculatorRequest(
operation=calc_server_pb2.CALCULATOR_OPERATION_ADD,
x=request.params.x,
y=request.params.y,
)
)
logging.info("Result: %d", response.result)
return None
Install the Skill into your solution.
Add the Calculator service to your solution
In order to use your new skill, there must be a Calculator service actually running in your solution for your skill to interact with.
- In Flowstate, click on the Services tab on the right sidebar, then click Add Service.
- Use the Search Bar to search for
calculator. - Select the
ai.intrinsic.calculator_service, then click Add to add it to your solution. - In the newly opened Add configuration to service dialog, set the "Service name" field to "calculator" to match the definition of
_SERVICE_SLOTinadder.py.
Error handling
As mentioned in the Skill overview section, a skill must raise one of the following error types:
InvalidSkillParametersError: If the arguments provided to skill parameters are invalid.SkillCancelledError: If the skill is aborted due to a cancellation request.SkillError: Errors encountered while executing methods of this interface.
For example let's modify the code above to handle invalid, non-numerical, input parameter:
def execute(
self,
request: skill_interface.ExecuteRequest[adder_pb2.AdderParams],
context: skill_interface.ExecuteContext,
) -> None:
# Validate input parameters
try:
_x = int(request.params.x)
_y = int(request.params.y)
except ValueError as err:
raise skill_interface.InvalidSkillParametersError(
f"Invalid input parameters: {err}")
stub = make_grpc_stub(context.resource_handles[_SERVICE_SLOT])
response = stub.Calculate(
calc_server_pb2.CalculatorRequest(
operation=calc_server_pb2.CALCULATOR_OPERATION_ADD,
x=request.params.x,
y=request.params.y,
)
)
logging.info("Result: %d", response.result)
return None