In general you never want .eval, those are just for debugging and should never be in a final model.
PyTensor has utilities to do loops with symbolic expressions which you can read about here: scan – Looping in PyTensor — PyTensor dev documentation
In general PyTensor has alternatives for most numpy functions with the same name that you should use when working with PyMC models.
The conventional import is something like import pytensor.tensor as pt and then wherever you would usually write np.foo(...) you instead do pt.foo(...)