Replies: 5 comments 8 replies
-
Adapter A (instanced)We have a class Example(BaseAdapter):
model_id = "example"
model_name = "Example"
concentrations = { "H20": 1, ... }
async def run(self) -> dict[str, float]:
# self.concentrations are user-provided in this context
new_concs = my_example_sim(self.concentrations)
return new_concsDepending on how we do the parameters, we can have one of the following: # Pydantic
class ExampleParameters(BaseParameters):
some_param: int = Parameter(100, label="Some parameter", description="It does something", unit="?")
class Example(BaseAdapter)
model_id = "example"
model_name = "Example"
concentrations = { "H20": 1, ... }
parameters: ExampleParameters
async def run(self) -> dict[str, float]:
# self.concentrations are user-provided in this context
# self.parameters are user-provided and validated before calling this method
new_concs = my_example_sim(
self.concentrations,
some_param=self.parameters.some_param # Note: we can't write self.some_param in this scheme
)
return new_concs# Custom
class Example(BaseAdapter)
model_id = "example"
model_name = "Example"
concentrations = { "H20": 1, ... }
some_param: int = Parameter(100, label="Some parameter", description="It does something", unit="?")
async def run(self) -> dict[str, float]:
# self.concentrations are user-provided in this context
# self.[parameter] are user-provided and validated before calling this method
new_concs = my_example_sim(
self.concentrations,
some_param=self.some_param
)
return new_concs |
Beta Was this translation helpful? Give feedback.
-
Adapter B (class methods)We don't want state, so we can make use of class Example(BaseAdapter):
model_id = "example"
model_name = "Example"
concentrations = { "H20": 1, ... }
@classmethod
async def run(self, concentrations: dict[str, float]) -> dict[str, float]:
# self.concentrations are user-provided in this context
new_concs = my_example_sim(concentrations)
return new_concsWith parameters. (Either parameter scheme fits this style) # Pydantic
class ExampleParameters(BaseParameters):
some_param: int = Parameter(100, label="Some parameter", description="It does something", unit="?")
class Example(BaseAdapter)
model_id = "example"
model_name = "Example"
concentrations = { "H20": 1, ... }
@classmethod
async def run(self, concentrations: dict[str, float], parameters: ExampleParameters) -> dict[str, float]:
# self.concentrations are user-provided in this context
# self.parameters are user-provided and validated before calling this method
new_concs = my_example_sim(
concentrations,
some_param=parameters.some_param
)
return new_concsThis scheme requires that the user only use |
Beta Was this translation helpful? Give feedback.
-
Adapter C (decorated functions)We can use Flask/FastAPI-style decorations to define the run function. That way we can get class-method style without using class methods. from acidwatch import Adapter, Parameter, BaseParameters
adapter = Adapter("arcs", name="ARCS", concentrations={"H2O": 1, ...})
@adapter.set_run
async def run(concentrations: dict[str, float]) -> dict[str, float]:
new_concs = my_example_sim(concentrations)
return new_concsor, of course: from acidwatch import Adapter, Parameter, BaseParameters
async def run(concentrations: dict[str, float]) -> dict[str, float]:
new_concs = my_example_sim(concentrations)
return new_concs
adapter = Adapter("arcs", name="ARCS", concentrations={"H2O": 1, ...}, run=run)
# Maybe:
# register_adapter("arcs", name="ARCS", concentrations={"H2O": 1, ...}, run=run)For parameters we can use the same scheme where it's a separately defined class whose instance is passed to |
Beta Was this translation helpful? Give feedback.
-
Slight side note. Perhaps Display name over model name as to be explicit about usage. We could consider a short (e.g. tooltip) and long description (we really don't have enough info about the models currently, we should support that). And we would like to include version of the model. We might want to embed this in the metadata though from the adapter, as it might not be available until the run function is executed (as in the response of the web call includes the version). I think it would be fair to require version in some form.
What would be the reason to expect default concentrations? Could we not have the same default concentrations for all? (and only use the default values for the components that the model accepts, and 0 if we haven't specified one). I think for the user in the frontend it would be beneficial to both keep order and default concentrations identical for all models. Considering example B, they would be a list of concentrations instead. Perhaps even call them # Pydantic
class ExampleParameters(BaseParameters):
some_param: int = Parameter(100, label="Some parameter", description="It does something", unit="?")
class Example(BaseAdapter)
model_id = "example"
model_name = "Example"
accepted_concentrations = ["H2O", ...]
@classmethod
async def run(self, concentrations: dict[str, float], parameters: ExampleParameters) -> dict[str, float]:
# self.concentrations are user-provided in this context
# self.parameters are user-provided and validated before calling this method
new_concs = my_example_sim(
concentrations,
some_param=parameters.some_param
)
return new_concsI just don't like to have the |
Beta Was this translation helpful? Give feedback.
-
|
A thought on the authentication and authorization topic. I think we can keep things fairly close to what they are today, and use the On-Behalf-Of (OBO) flow. That is, we have authentication in the frontend and authorization in the backend. Frontend allows users to prove who they are, add a token, which is sent to backend with every request. The backend then tries to retrieve an on behalf of token in order to call other services. In this case the Adapters only need to provide their scope. The backend performs the obo flow and retrieves an access token. The run method in the adapter must then allow for an optional token to be passed with it, if that is necessary, in order to include it whenever it calls downstream apis. @fastapi_app.get("/run")
def run():
adapter = ArcsAdapter() # or provide concs and settings here..
token = acquire_token_for_downstream_api(adapter.scope) if adapter.require_auth else None
adapter.run(concs, settings, token)
def acquire_token_for_downstream_api(scope: str, jwt_token: str) -> str:
result = confidential_app.acquire_token_on_behalf_of(
user_assertion=jwt_token, scopes=[scope]
)
if "error" in result:
logger.error(result["error"])
raise HTTPException(401, result["error_description"])
return result["access_token"] # type: ignore(we probably would like to perform validation checks on tokens coming from the front end at all times, not sure if that is the case. We should have a Depends on all urls, which, in the case that a token is included, checks that it is valid. But that is a different topic perhaps, and is not specific for adapters..) |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Refer to #380 on how to define parameters. This is a discussion about how to define the rest.
An adapter adapts a CO2 model for use in Acidwatch. The adapter therefore defines some static metadata:
arcs)ARCSfor GUI)And two functions:
runfunction, that takes concentrations and parameters and outputs new concentrations alongside metadata.Functions should exist that can convert a given adapter to a JSON-able representation so we can display it in the frontend. Bonus points if it's possible to invoke via CLI (
acidwatch run arcs NO=2 temperature=10)Beta Was this translation helpful? Give feedback.
All reactions