simodocs

Building with Simo

Agents: the next tool call, in a loop

An agent step is a decision: which action next, with which arguments? Simo answers it as a Choice over your actions, generates the arguments for the chosen one in the same request, and gives you a calibrated probability. Below your threshold, the agent stops and hands over instead of guessing.

Decide one step

Python
from simoai import Client, Agent, Extract, Fill, Noul

c = Client()
agent = Agent(c,
    actions={"lookup_order": "look up an order by its number",
             "issue_refund": "refund an order",
             "ask_customer": "ask the customer a question",
             "close_ticket": "close the ticket"},
    args={"lookup_order": {"order_id": Extract("the order number to use, exactly as written", max_tokens=8)},
          "issue_refund": {"order_id": Extract("the order number"),
                           "amount": Fill("the refund amount", kind="number")},
          "ask_customer": {"question": Fill("the question to ask, one short sentence", max_tokens=32)}},
    question="Following the policy and what has happened so far, what is the agent's next action?",
    act_at=0.8,                                  # below this, stop and hand over
    extra={"angry": Noul("The customer is angry")})

d = agent.decide({"policy": POLICY, "ticket": ticket_text, "history": "(nothing yet)"})
d.action, d.args, d.p     # "lookup_order", {"order_id": "A-1042"}, 0.97
d.confident               # True: p >= act_at
d.probabilities           # over every action
d.call                    # "lookup_order(order_id='A-1042')"
d.extra                   # {"angry": False}

extra adds side questions answered in the same request, such as a mood flag for escalation. The agent's own question id is action, so don't use it in extra.

Run a loop

Give agent.run an environment with state(), act(action, args) and done:

Python
from simoai import History

class Env:
    def __init__(self):
        self.history = History()
        self.done = False

    def state(self):                    # may also return (state, media) for screenshots
        return {"policy": POLICY, "ticket": ticket_text, "history": str(self.history)}

    def act(self, action, args):
        result = TOOLS[action](**args)
        self.history.add(f"{action}({args})", result)
        self.done = action == "close_ticket"
        return result

run = agent.run(Env(), max_steps=20)
run.finished, run.reason, run.calls     # True, "done", ["lookup_order(...)", "issue_refund(...)", ...]

agent.run also takes on_step (called after each step; return False to stop) and on_uncertain (called with a decision below act_at; return True to act on it anyway). run.steps holds each decision and what act returned.

run.reason is one of:

Reason Meaning
done The environment reported done
uncertain A step fell below act_at: hand over to a person
max_steps Hit max_steps
stopped on_step returned False

Without the Agent class

Under the hood a step is a single Choice with args. Over HTTP:

JSON
{
  "model": "simo-1-pro",
  "state": {"policy": "...", "ticket": "...", "history": "(nothing yet)"},
  "questions": {
    "action": {
      "type": "choice",
      "instructions": "Following the policy and what has happened so far, what is the agent's next action?",
      "criteria": {"lookup_order": "look up an order by its number", "issue_refund": "refund an order",
                   "ask_customer": "ask the customer a question", "close_ticket": "close the ticket"},
      "args": {"lookup_order": {"order_id": {"type": "extract", "instructions": "the order number to use, exactly as written",
                                             "kind": "text", "many": false, "max_tokens": 8}}}
    }
  }
}

Read answers.action.choice, its probability in answers.action.probabilities, and the arguments in answers.action.args. See the tool-calling agent example and the browser agent example.