--- layout: default title: "Agent" parent: "Design Pattern" nav_order: 6 --- # Agent Agent is a powerful design pattern in which nodes can take dynamic actions based on the context. The core of building **high-performance** and **reliable** agents boils down to: 1. **Context Management:** Provide *relevant, minimal context.* For example, rather than including an entire chat history, use [RAG](./rag.md) to retrieve only the most relevant parts. Even if LLMs have larger context windows, they can exhibit the ["lost in the middle"](https://arxiv.org/abs/2307.03172), often focusing on the start and end portions of the context while disregarding the middle. 2. **Action Space:** Define *a well-structured and unambiguous* set of actions. Avoid overlapping actions like `read_databases` and `read_csvs`. Instead, unify data sources (e.g., import CSVs into a database) and design a single action. That action can be parameterized (e.g., a search string) or made programmable (e.g., SQL queries).
Agent Implementation Steps: 1. **Context and Action:** Implement nodes that supply context and perform actions. 2. **Branching:** Connect action nodes with an agent node, using [branching](../core_abstraction/flow.md) to direct the flow to other action nodes, and potentially loop back. 3. **Agent Node:** Provide a prompt—for example: ```python """ Here is the context: {context} Here are the actions: 1. Name: search Description: Use web search to get results Parameters: query: str of what to search 2. Name: answer Description: Conclude based on the results Parameters: result: str of what to answer Now decide your action by returning: ```yaml thinking: | Based on the context, ... action: search or answer parameters: ... ```""" ``` ### Example: Search Agent This agent: 1. Decides whether to search or answer 2. If searches, loops back to decide if more search needed 3. Answers when enough context gathered ```python class DecideAction(Node): def prep(self, shared): context = shared.get("context", "No previous search") query = shared["query"] return query, context def exec(self, inputs): query, context = inputs prompt = f""" Given input: {query} Previous search results: {context} Should I: 1) Search web for more info 2) Answer with current knowledge Output in yaml: ```yaml action: search/answer reason: why this action search_term: search phrase if action is search ```""" resp = call_llm(prompt) yaml_str = resp.split("```yaml")[1].split("```")[0].strip() result = yaml.safe_load(yaml_str) assert isinstance(result, dict) assert "action" in result assert "reason" in result assert result["action"] in ["search", "answer"] if result["action"] == "search": assert "search_term" in result return result def post(self, shared, prep_res, exec_res): if exec_res["action"] == "search": shared["search_term"] = exec_res["search_term"] return exec_res["action"] class SearchWeb(Node): def prep(self, shared): return shared["search_term"] def exec(self, search_term): return search_web(search_term) def post(self, shared, prep_res, exec_res): prev_searches = shared.get("context", []) shared["context"] = prev_searches + [ {"term": shared["search_term"], "result": exec_res} ] return "decide" class DirectAnswer(Node): def prep(self, shared): return shared["query"], shared.get("context", "") def exec(self, inputs): query, context = inputs return call_llm(f"Context: {context}\nAnswer: {query}") def post(self, shared, prep_res, exec_res): print(f"Answer: {exec_res}") shared["answer"] = exec_res # Connect nodes decide = DecideAction() search = SearchWeb() answer = DirectAnswer() decide - "search" >> search decide - "answer" >> answer search - "decide" >> decide # Loop back flow = Flow(start=decide) flow.run({"query": "Who won the Nobel Prize in Physics 2024?"}) ```