You are given an LLM client whose responses may contain text blocks or one or more tool-call blocks. Build an agent that answers a user question by repeatedly calling the model, executing requested tools, retur…
Question
You are given an LLM client whose responses may contain text blocks or one or more tool-call blocks. Build an agent that answers a user question by repeatedly calling the model, executing requested tools, returning tool results, and stopping when the model produces a final answer.
Assume two tools are available: a stock-price lookup and a shares-outstanding lookup. A question may require one lookup, several independent lookups, or combining results such as market capitalization.
## Requirements
- Preserve the complete conversation history, including assistant tool calls and matching tool results.
- Execute every tool call in a response; do not silently discard parallel calls.
- Match each result to its tool-call identifier.
- Stop only on a final model response, and return its text.
- Return a JSON error tool result for unknown tools, malformed arguments, or unknown tickers, preserving the original tool-call identifier. Continue the conversation rather than leaking an exception from the tool dispatcher.
- Enforce a configurable turn limit so a faulty model cannot loop forever.
```python
def run_agent(question: str, client: ModelClient) -> str: ...
```
The supplied local `api` module provides `Client`, `TOOLS`, and `run_tool(name, arguments)`. Both tools accept exactly `{"ticker": <string>}`; ticker matching is case-insensitive. Tool results are JSON strings. Responses have `stop_reason` (`tool_use` or `end_turn`) and a `content` list of blocks. Text blocks expose `type="text"` and `text`; tool-call blocks expose `type="tool_use"`, `id`, `name`, and `input`.
Call `client.messages.create(model="local", messages=history, tools=TOOLS)`. Return all tool results in the next user message as blocks with `type="tool_result"`, `tool_use_id`, and `content`. The supplied client enforces the fixture's model-call budget (one to eight turns). An unsupported stop reason is an error; exhaustion must terminate with an error. An `end_turn` response returns its text blocks concatenated in order, including the empty string when there are none. Implement `run_agent`; the supplied case adapter invokes it for each check.
## Edge cases
Consider an empty response, multiple text blocks, several tool calls in one turn, a tool exception, repeated calls, and a model that never reaches a final answer.
## Staged follow-ups
1. Minimize model round trips when independent tools can run together.
2. Add timeouts, retries, cancellation, and observable progress without duplicating side effects.
3. Define an evaluation set and metrics for answer correctness, tool efficiency, and unsafe actions.
4. Explain how you would defend the loop from prompt injection contained in tool output.The public brief is identical for people and crawlers. Account access changes tools, not the indexed description.