To solve this problem, we first need to convert the natural language description into a symbolic representation. Let's denote the number of small smoothies as `x1` and the number of large smoothies as `x2`.

The objective function is to maximize profit, which can be represented algebraically as `3*x1 + 5*x2`, since each small smoothie generates $3 in profit and each large smoothie generates $5 in profit.

The constraints are based on the availability of ice cream and peanut butter. For ice cream, we have `2*x1 + 3*x2 <= 20` because each small smoothie requires 2 units of ice cream and each large smoothie requires 3 units, with a total of 20 units available. For peanut butter, we have `1*x1 + 2*x2 <= 18` because each small smoothie requires 1 unit of peanut butter and each large smoothie requires 2 units, with a total of 18 units available.

Additionally, since the number of smoothies cannot be negative, we also have non-negativity constraints: `x1 >= 0` and `x2 >= 0`.

Thus, our symbolic representation is:

```json
{
    'sym_variables': [('x1', 'number of small smoothies'), ('x2', 'number of large smoothies')],
    'objective_function': '3*x1 + 5*x2',
    'constraints': ['2*x1 + 3*x2 <= 20', '1*x1 + 2*x2 <= 18', 'x1 >= 0', 'x2 >= 0']
}
```

To solve this problem using Gurobi in Python, we use the following code:

```python
from gurobipy import *

# Create a new model
m = Model("Smoothie_Problem")

# Define variables
x1 = m.addVar(lb=0, vtype=GRB.CONTINUOUS, name="small_smoothies")
x2 = m.addVar(lb=0, vtype=GRB.CONTINUOUS, name="large_smoothies")

# Set the objective function
m.setObjective(3*x1 + 5*x2, GRB.MAXIMIZE)

# Add constraints
m.addConstr(2*x1 + 3*x2 <= 20, "ice_cream_constraint")
m.addConstr(x1 + 2*x2 <= 18, "peanut_butter_constraint")

# Optimize model
m.optimize()

# Print solution
if m.status == GRB.OPTIMAL:
    print(f"Optimal solution: x1 = {x1.x}, x2 = {x2.x}")
    print(f"Maximum profit: {m.objVal}")
else:
    print("No optimal solution found")
```