<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Binome - Medium]]></title>
        <description><![CDATA[Notes on Agentic AI and Cloud Infrastructure - Medium]]></description>
        <link>https://medium.com/binome?source=rss----35517879563d---4</link>
        <image>
            <url>https://cdn-images-1.medium.com/proxy/1*TGH72Nnw24QL3iV9IOm4VA.png</url>
            <title>Binome - Medium</title>
            <link>https://medium.com/binome?source=rss----35517879563d---4</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Tue, 10 Nov 2026 15:04:28 GMT</lastBuildDate>
        <atom:link href="https://medium.com/feed/binome" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[Prove You’re Better Than a Mac Mini]]></title>
            <link>https://medium.com/binome/prove-youre-better-than-a-mac-mini-0484b0dde125?source=rss----35517879563d---4</link>
            <guid isPermaLink="false">https://medium.com/p/0484b0dde125</guid>
            <category><![CDATA[harness]]></category>
            <category><![CDATA[vibe-coding]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[ai-agent]]></category>
            <dc:creator><![CDATA[Craig Li, Ph.D]]></dc:creator>
            <pubDate>Sun, 11 Oct 2026 10:58:56 GMT</pubDate>
            <atom:updated>2026-10-11T11:00:11.123Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*bYOy2N40djMu4ukG115p0w.png" /></figure><p><em>TL;DR: Be proactive and master the art of harnessing coding agents.</em></p><p>I sometimes ask my mentee a question during mock interviews: <strong>“Prove to me that you’re better than a Mac Mini.”</strong></p><p>It sounds harsh, maybe even politically incorrect. But as a startup CTO, I think it’s becoming a very real question. Some of our mentees have gone on to land jobs at places like JPMorgan, engineer in hudge fund, and promising startups, and I genuinely believe the expectations for junior engineers are changing faster than most people realise.</p><p>Let me explain why.</p><h3>My coding setup: 3 machines, 7 coding agents</h3><p>Right now, I have three Apple machines running coding agents, each with its own responsibilities:</p><ul><li><strong>Mac Mini M4, 16GB:</strong> Two Claude Code instances focused on feature implementation and infrastructure improvements.</li><li><strong>Mac Mini, 16GB:</strong> Two Claude Code instances handling technical debt cleanup and reliability improvements.</li><li><strong>MacBook Pro M4 Pro, 48GB:</strong> Claude Code instances working on features and hotfixes, alongside Codex for automated QA against our development environment.</li></ul><p>These agents work in parallel, handling everything from implementing features to maintaining code quality and running tests. The goal is to keep development moving around the clock, even when I’m not sitting in front of a computer.</p><p>I manage the sessions through T3 Code. It’s in alpha, still some bugs, but it allows me to check progress, review what agents are doing, and provide instructions from my phone. I can do this on the train, between meetings, or even while having dinner with friends. That’s probably not the healthiest work-life balance, but it’s the reality of building an early-stage startup. As a CTO, work tends to follow you everywhere.</p><p>Now think about the economics. A Mac Mini costs a few hundred pounds. Add a Claude Max subscription, configure the environment properly, and suddenly you have a coding agent that can implement features, write tests, fix bugs and work through technical debt. It doesn’t get tired, doesn’t complain about repetitive tasks, and can keep working as long as the environment and task allow it.</p><p>This leads to an uncomfortable question: <strong>if your primary value as a software engineer is taking a well-defined ticket and turning it into code, what makes you more valuable than that Mac Mini?</strong></p><p>I don’t think the answer is writing better code or typing faster. Increasingly, I think it comes down to two skills.</p><h3>1. Be proactive: see problems before someone assigns them</h3><p>Coding agents are already remarkably good at implementation, especially when given precise instructions and sufficient context. What they still struggle with is understanding the bigger picture of a product and independently recognising what should be improved next.</p><p>For example, I might be looking at our operational dashboard and suddenly think: <em>We’re missing a chart that shows the end-to-end journey of a message. That would make debugging much easier.</em> Or while working with our Telegram integration, I might realise that adding a search tool would give agents much better context from previous conversations.</p><p>Another day, I might get frustrated that changing LLM models for different clients requires too many configuration changes. That frustration becomes an idea: <em>Why don’t we introduce model aliases so we can switch the underlying model without touching every client configuration?</em></p><p>These aren’t tasks someone assigned to me. They come from using the product, understanding the architecture, noticing friction and asking whether something could work better.</p><p>Of course, you can ask a coding agent to review a system and suggest improvements. Sometimes it will come up with genuinely useful ideas. But someone still needs to understand the business, recognise which problems matter, connect different parts of the product and decide what deserves attention.</p><p><strong>That’s what I mean by being proactive.</strong> Don’t wait for someone to create a Jira ticket. Explore the product, question existing decisions, identify bottlenecks and propose improvements. The most valuable engineer isn’t necessarily the person who finishes the most tickets, but the one who discovers what the team should be working on.</p><h3>2. Learn to harness coding agents, not compete with them</h3><p>Here’s another question I would ask during an interview: <strong>“If I give you two or three Mac Minis and expect five good PRs every day, how would you make that happen?”</strong></p><p>The answer isn’t simply opening five Claude Code sessions and giving each one a task. Anyone can do that. The challenge is building an environment where multiple agents can work productively without constantly stepping on each other’s toes.</p><p>You need to think about how tasks are decomposed, how to isolate coding environments with separate worktrees or repositories, and how to provide each agent with the right context. You also need automated validation: linting, unit tests, integration tests, end-to-end tests and perhaps another agent reviewing the code before a human looks at it.</p><p>And what happens when things go wrong? What if two agents modify the same interface? What if one introduces a change that breaks another feature? What if the tests pass, but the implementation fundamentally misunderstands the product requirements?</p><p>This is where engineering experience and system design become important. You’re no longer just writing code; you’re designing a development process that allows machines to write code reliably.</p><p>I think of this as <strong>harness engineering</strong>. The coding agent is the engine, but the harness determines how effectively that engine can operate. A powerful model with poor context, unclear instructions and unreliable testing can create more problems than it solves. A well-designed harness, on the other hand, can turn a few relatively inexpensive machines into a surprisingly productive engineering setup.</p><p>Five PRs a day sounds impressive, but five poorly designed PRs just create technical debt faster. The objective isn’t to maximise the amount of code generated. It’s to maximise the amount of useful, reliable software delivered.</p><h3>AI hasn’t changed software engineering. It has accelerated it.</h3><p>Despite all the excitement around coding agents, I don’t think AI has fundamentally changed the principles of software engineering. Good architecture still matters. So do testing, reliability, maintainability, security and understanding what customers actually need.</p><p>What has changed dramatically is the speed at which we can explore ideas, implement solutions and get feedback.</p><p>Previously, you might spend several weeks implementing an architectural approach before discovering its limitations. Today, you can discuss multiple approaches with an agent, ask it to compare their trade-offs, prototype different implementations and evaluate the results in a fraction of the time.</p><p>But there’s an important consequence that I think we aren’t discussing enough.</p><p>Traditionally, junior engineers develop into senior engineers through experience. They write code, encounter problems, make mistakes, debug complex systems and gradually build an intuition for architecture and engineering trade-offs. That experience is what eventually allows them to make good technical decisions.</p><p>Now, coding agents can handle much of the implementation work that used to provide those learning opportunities. If junior engineers simply accept whatever the agent produces, they might become extremely productive without developing the understanding needed to become senior engineers.</p><p><strong>Being able to generate code is not the same as understanding software engineering.</strong></p><p>I think the solution is to become much more deliberate about learning. When an agent suggests an architecture, ask why. Ask it to propose alternatives, challenge its own design and explain what might fail as the system scales. Discuss the trade-offs, experiment with different approaches and compare the results.</p><p>Most importantly, make the final judgment yourself and understand the reasoning behind it. Every interaction with an agent should be an opportunity to develop your own engineering knowledge, rather than simply outsourcing another decision.</p><p>AI can make you faster, but it doesn’t automatically make you better.</p><h3>So, can you prove you’re better than a Mac Mini?</h3><p>I don’t believe junior software engineers are becoming irrelevant. In fact, I think this is an incredibly exciting time to enter the industry. You have access to tools that allow you to build, experiment and learn at a pace that would have been unimaginable a few years ago.</p><p>But the bar is changing. Writing code is becoming less of a differentiator, while recognising valuable problems, understanding systems, making sound decisions and orchestrating AI agents are becoming increasingly important.</p><p>If I were interviewing a junior engineer today, I wouldn’t be particularly interested in whether they could write Python faster than Claude Code. I’d want to know whether they could take an ambiguous product problem, turn it into a clear technical plan, coordinate a few coding agents to implement it, and take responsibility for the quality of the result.</p><p>Even better, I’d want them to come back with an improvement I hadn’t thought of.</p><p>So when I ask, <strong>“Prove you’re better than a Mac Mini,”</strong> I’m not asking you to compete against a machine at writing code. I’m asking whether you can bring the curiosity, initiative and judgment that make those machines genuinely useful.</p><p>Because the future of software engineering isn’t about how much code you can write yourself. It’s about how much you can accomplish, how well you understand what you’re building, and how effectively you can turn your ideas into reliable software.</p><p><strong>Don’t compete with the Mac Mini. Learn to master it.</strong></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=0484b0dde125" width="1" height="1" alt=""><hr><p><a href="https://medium.com/binome/prove-youre-better-than-a-mac-mini-0484b0dde125">Prove You’re Better Than a Mac Mini</a> was originally published in <a href="https://medium.com/binome">Binome</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Developing AI-Powered Assistants with Graphite: Lessons from My Internship]]></title>
            <link>https://medium.com/binome/developing-ai-powered-assistants-with-graphite-lessons-from-my-internship-7b655c03d3a6?source=rss----35517879563d---4</link>
            <guid isPermaLink="false">https://medium.com/p/7b655c03d3a6</guid>
            <category><![CDATA[agentic-workflow]]></category>
            <category><![CDATA[agents]]></category>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[llm-agent]]></category>
            <category><![CDATA[llm]]></category>
            <dc:creator><![CDATA[Chung Ho Lun]]></dc:creator>
            <pubDate>Sat, 04 Oct 2025 00:46:42 GMT</pubDate>
            <atom:updated>2025-10-04T07:35:14.016Z</atom:updated>
            <content:encoded><![CDATA[<p>During my internship at <a href="https://binome.dev/">Binome</a>, I had the opportunity to dive deep into Graphite, an event-driven AI agent framework designed for building intelligent workflows. Over the past few months, I worked on two exciting projects: a <strong>Smart Contract Agent</strong> that assists developers in writing and deploying smart contracts, and an Internal <strong>Graphite Workflow Builder</strong> that can generate complete project structures and runnable demos from simple user queries. In this post, I’d like to share my journey, highlighting the challenges I faced, the lessons I learned, and why this internship has been such a valuable experience for my growth and future career.</p><p><a href="https://github.com/ayomaska18/Smart_Contract_Agent">GitHub - ayomaska18/Smart_Contract_Agent: An AI-powered smart contract development assistant developed using Binome&#39;s Graphite workflow framework</a></p><h3>Understanding Graphite</h3><p><a href="https://github.com/binome-dev/graphite"><strong>Graphite</strong></a><strong> is an event-driven framework that enables developers to create AI agents or workflows composed of modular components, known as nodes and topics.</strong> Each node can perform specific actions, respond to events, or communicate with other nodes. Topics are used for message queuing for the workflow, essential for Pub/Sub interactions. Core Features, including observability, idempotency, and auditability, make it straightforward to build, trace, and test workflows and agents with fully customisable functionality.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/875/0*bg14VtZhIjKahLCe.png" /></figure><p>During my internship, understanding Graphite’s architecture was key to designing assistants that could handle real-world tasks like building a whole smart contract, generating and deploying a workflow, or building entire project structures from natural language queries.</p><h3>Why Smart Contract Assistant?</h3><p>When my internship started in mid-June, one of the co-founders and the technology lead at Binome asked me what kind of projects or applications I wanted to work on — whether it was model training or building agentic applications. At that time, Agents, RAGs, and MCPs, all these terms were all completely new to me, so I decided to dive deeper into them. He then tasked me with coming up with a project idea. It could be in finance, healthcare, or any domain that interests me. Immediately, crypto came to mind, which has always been interesting for me. But I wondered: how could I integrate cryptocurrencies with agents? What value could AI bring to the world of crypto? How could I build applications that improve the experience for users, traders, and builders?</p><p>The answer became clear: I wanted to build tools and infrastructure that could make blockchain and smart contracts more accessible to people who might not be familiar with crypto. While platforms like Pump.fun allow users to deploy tokens easily, they often lack flexibility and customisation. On the other hand, tools like Remix provide full control for writing smart contracts with languages like Solidity, with manual compilation and deployment of the contract, but they can be intimidating and unintuitive for newcomers. My goal was to bridge this gap — creating applications that combine the power of AI agents with blockchain technology, enabling users to interact with, create, and deploy smart contracts in a more intuitive and guided way without extensive knowledge of coding.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*Krjz8jV4LiqzFc5Q" /><figcaption>Image from Pump.fun</figcaption></figure><h3>How does the Smart Contract Assistant work?</h3><p>The Smart Contract Assistant is designed to guide users through the entire smart contract lifecycle, from understanding requirements to deploying contracts on a blockchain. It works as an AI-powered agent composed of multiple LLM nodes and function call nodes, each responsible for a specific task.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*t3Ymhjq1dwPloRz83zIbvw.png" /><figcaption>Workflow for Smart Contract Assistant</figcaption></figure><h4>Understanding Users’ Requirements</h4><p>The workflow begins by receiving a user query from the agent_input_topic. Queries can be simple, such as “What kind of smart contract can you generate or deploy?” or “How do I deploy my smart contract to the Ethereum blockchain?” These queries are handled by the reasoning_node. If no special action is required, the reasoning_node directly provides an answer back to the user via the agent_output_topic.</p><p>However, for more complex queries like “Can you generate me an ERC-20 token with name Binome, and with supply 10000 with burnable features”, this is where the fun begins, instead of directly going to the agent_input_topic, the reasoning_node will proceed with the request to the ActionNode, which will then call the Required function tools from the MCP server, and return the desired result to the user.</p><p>Example reasoning response:</p><pre>{<br>  &quot;reasoning&quot;: &quot;The user has provided specific details for an ERC20 token: Name: MyToken, Symbol: MTK, Initial supply: 1000000, Features: mintable (which requires ownable). I have all the necessary information to generate this contract.&quot;,<br>  &quot;requires_tool_call&quot;: true,<br>  &quot;tool_call_reasoning&quot;: &quot;Need to call generate_erc20_contract with the specified parameters to create the ERC20 token contract&quot;,<br>  &quot;tool_result&quot;: null,<br>  &quot;confidence&quot;: 0.95,<br>  &quot;requires_deployment&quot;: false<br>}</pre><h4>MCP server</h4><p>Graphite allows nodes to connect to any MCP server with ease, just by using the MCPTool.builder() method to connect to your dedicated MCP server as a tool, and can be injected into any nodes that you wish to call the tools that you defined on the MCP server.</p><pre>mcp_config: Dict[str, StreamableHttpConnection] = {<br>        &quot;smart-contract-server&quot;: StreamableHttpConnection(<br>            url=mcp_server_url,<br>            transport=&quot;http&quot;<br>        )<br>    }<br>mcp_tool = await MCPTool.builder().connections(mcp_config).a_build()</pre><p>When the reasoning_node detects user wanting to generate/compile a contract, it will go down the action route, the action node will prepare the input params for the tool_execution_node to call the MCP tools.</p><p>Contract generation:</p><pre>generate_erc20_contract(contract_name=&quot;MyToken&quot;, token_name=&quot;My Token&quot;, token_symbol=&quot;MTK&quot;, ...)</pre><p>Contract compilation:</p><pre>compile_contract(solidity_code=&quot;pragma solidity ^0.8.0; contract...&quot;)</pre><h4>Human In The Loop (HITL)</h4><p>Once a user has compiled their contract or provided an already compiled contract and requests deployment to a blockchain (currently supporting Ethereum Sepolia), we will proceed to the deployment route, where deployment_request_node will prepare the necessary deployment details, including gas price, chain ID, constructor arguments, etc. And prepare_deployment_node will be calling the prepare_deployment_transactiontool from the MCP server using the input details, which will send from the prepare_deployment_output_topic to the frontend, and require the user to sign the transaction to pay the gas to deploy the contract.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/948/1*Xg5SsmDLa9sk9aDlt8Cbug.png" /><figcaption>Request user to sign the transaction for deployment</figcaption></figure><p>Once the user has signed the transaction, the signed transaction will be sent back to the assistant through the deployment_approval_topic and the workflow will continue, the deployment_approval_nodewill prepare the input details and call the broadcast_signed_transaction tool to broadcast the signed transaction to the blockchain from the broadcast_deployment_node.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/925/1*37SHJyYpCEO1vmhmO72x7Q.png" /><figcaption>Successful Deployment Response from the Assistant</figcaption></figure><h4>Final Response</h4><p>The final response will depend on what the user queries. Here are the possible query responses from the assistant:</p><p>Greetings/general queries:</p><pre>{<br>  &quot;status&quot;: &quot;completed&quot;,<br>  &quot;summary&quot;: &quot;Hello! I&#39;m your Smart Contract Assistant. I can help you generate ERC20 tokens, ERC721 NFTs, compile contracts, and handle deployments. What would you like to work on?&quot;,<br>  &quot;results&quot;: null,<br>  &quot;next_actions&quot;: [&quot;Ask me to generate a token&quot;, &quot;Request contract compilation&quot;, &quot;Get help with deployment&quot;],<br>  &quot;artifacts&quot;: null,<br>  &quot;warnings&quot;: null<br>}</pre><p>Contract generation with tool results:</p><pre>{<br>  &quot;status&quot;: &quot;completed&quot;, <br>  &quot;summary&quot;: &quot;Successfully generated MyToken ERC20 contract with mintable functionality. The contract includes standard ERC20 features plus minting capability restricted to the owner.&quot;,<br>  &quot;results&quot;: &quot;{\&quot;contract_type\&quot;: \&quot;ERC20\&quot;, \&quot;contract_name\&quot;: \&quot;MyToken\&quot;, \&quot;features\&quot;: [\&quot;mintable\&quot;, \&quot;ownable\&quot;], \&quot;solidity_code\&quot;: \&quot;pragma solidity ^0.8.19;\\n\\nimport &#39;@openzeppelin/contracts/token/ERC20/ERC20.sol&#39;;\\nimport &#39;@openzeppelin/contracts/access/Ownable.sol&#39;;\\n\\ncontract MyToken is ERC20, Ownable {\\n    constructor() ERC20(&#39;MyToken&#39;, &#39;MTK&#39;) {}\\n    \\n    function mint(address to, uint256 amount) public onlyOwner {\\n        _mint(to, amount);\\n    }\\n}\&quot;}&quot;,<br>  &quot;next_actions&quot;: [&quot;Compile the contract&quot;, &quot;Review the Solidity code&quot;],<br>  &quot;artifacts&quot;: [&quot;MyToken.sol&quot;],<br>  &quot;warnings&quot;: [&quot;Remember to compile before deployment&quot;]<br>}</pre><p>Successful deployment with tool results:</p><pre>{<br>  &quot;status&quot;: &quot;completed&quot;,<br>  &quot;summary&quot;: &quot;Successfully deployed MyToken contract! Your ERC20 token is now live on the blockchain and ready to use.&quot;,<br>  &quot;results&quot;: &quot;{\&quot;deployment_status\&quot;: \&quot;success\&quot;, \&quot;contract_name\&quot;: \&quot;MyToken\&quot;, \&quot;contract_address\&quot;: \&quot;0x742d35Cc6538C21f1b2cF9BCA59CDf3f3aDBc123\&quot;, \&quot;transaction_hash\&quot;: \&quot;0x1234567890abcdef...\&quot;, \&quot;block_number\&quot;: 18500000, \&quot;gas_used\&quot;: \&quot;1205843\&quot;}&quot;,<br>  &quot;next_actions&quot;: [&quot;Verify contract on Etherscan&quot;, &quot;Mint initial tokens&quot;, &quot;Set up token distribution&quot;],<br>  &quot;artifacts&quot;: [&quot;Contract Address: 0x742d35Cc6538C21f1b2cF9BCA59CDf3f3aDBc123&quot;, &quot;Deployment Transaction: 0x1234567890abcdef...&quot;],<br>  &quot;warnings&quot;: [&quot;Remember to verify your contract on Etherscan for transparency&quot;]<br>}</pre><p>The responses are well-structured by leveraging Pydantic model to reduce LLMs from hallucinating and ensure consistency.</p><h3>Any future developments?</h3><p>Yes, of course! There are a lot of improvements and features that can be added to the assistant.</p><ol><li><strong>Improving contract generation capability</strong></li></ol><p>The assistant only supports basic contract generation, including the ERC-20 standard and the ERC-721 standard. There is potential to expand its capabilities to generate more complex contracts, including stablecoins, Real World Assets (RWA), and Decentralised Autonomous Organizations (DAO), etc. As well as generating contracts on different non-ethereum-based chains, including Solana, which uses Rust for its smart contracts. For clients with specialized needs, custom contract generation can be added for clients that has a specific task that they want to execute on the blockchain.</p><p>2. <strong>Supporting multi-blockchain support</strong></p><p>Instead of only allowing users to deploy contracts on Ethereum Sepolia. Expanding this to include multiple Ethereum-based chains, such as Arbitrum and Base, as well as non-Ethereum-based chains like Solana and Hyperliquid, would significantly increase the assistant’s usability and appeal, which would then attract a wider range of audiences.</p><p>3. <strong>Automating security audits</strong></p><p>The idea is for the assistant to automatically check the generated contracts for common vulnerabilities, coding errors, or inefficiencies before deployment. This can be achieved by integrating existing smart contract analysis tools or frameworks, such as:</p><ul><li><strong>Slither</strong> — A static analysis framework for Solidity that detects vulnerabilities like reentrancy, uninitialized storage, or arithmetic issues.</li><li><strong>MythX</strong> — A cloud-based security analysis platform that can run automated tests against Ethereum smart contracts.</li></ul><p>By incorporating these tools, the agent could provide real-time alerts and suggestions, flagging risky code patterns and recommending safer alternatives. This ensures that even users without deep blockchain knowledge can deploy more secure and reliable smart contracts.</p><h3>Moving on to Graphite Workflow Builder…</h3><p>After completing my first project with the Smart Contract Assistant, I was given a second task — one that was both exciting and challenging. I was asked to build an assistant capable of generating an entire project demo using Graphite. Yes, you read that correctly: <strong>using Graphite to build a Graphite demo!</strong> This assistant was intended to showcase the framework to potential clients, investors, and even university students, making complex workflows easy to understand and interact with.</p><p>The challenging part is that we need to teach the LLM to understand the Graphite framework to accurately generate the demo.</p><h3>How does the Graphite Workflow now work?</h3><p>The workflow primarily consists of two routes: firstly, the general query route, which answers users&#39; questions without generating any demos; secondly, the generation route, which generates the entire project demo in one step based on user requirements.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*i5_dtxZ2muP8mRUtl7LLvw.png" /><figcaption>Graphite Workflow Builder workflow</figcaption></figure><h4>Planning</h4><p>The reasoning_node is responsible for planning out the project structure and required resources from the Graphite repo for generation.</p><p>Example reasoning_node response:</p><pre>{<br>  &quot;reasoning&quot;: &quot;Brief analysis of user requirements&quot;,<br>  &quot;requires_code_generation&quot;: true,<br>  &quot;required_resources&quot;: [&quot;specific_graphite_components_needed&quot;],<br>  &quot;code_generation_plan&quot;: &quot;specific code generation plan&quot;,<br>  &quot;required_files&quot;: {<br>    &quot;main.py&quot;: &quot;Main application code with core workflow logic&quot;,<br>    &quot;requirements.txt&quot;: &quot;Python dependencies needed for the project&quot;,<br>    &quot;README.md&quot;: &quot;Setup instructions and usage documentation&quot;<br>  },<br>  &quot;confidence&quot;: 0.95<br>}</pre><p>It lists out the required resources, which tells the data_retrieval_node what class, functions, or examples we need for generation, and the required_files field listing out what files will be included in the project folder.</p><h4><strong>Data Retrieval</strong></h4><p>The data_retrieval_node is responsible for preparing input parameters for the data_retrieval_tool_node, which calls a custom tool I defined called GraphiteTool. To enable quick and accurate retrieval of relevant resources from the Graphite codebase, I created an index using the Abstract Syntax Tree (AST) library to parse the entire repository. With a pre-defined summary of what resources can be fetched included in the prompt template, the data_retrieval_node can efficiently query the GraphiteTool with the correct parameters, allowing the assistant to provide precise and contextually relevant information from the codebase.</p><p>Example Graphite repo index:</p><pre>&quot;workflows&quot;: {<br>    &quot;grafi_workflows_workflow&quot;: {<br>      &quot;path&quot;: &quot;grafi/workflows/workflow.py&quot;,<br>      &quot;file_name&quot;: &quot;workflow.py&quot;,<br>      &quot;size_bytes&quot;: 3762,<br>      &quot;classes&quot;: [<br>        {<br>          &quot;name&quot;: &quot;Workflow&quot;,<br>          &quot;line_number&quot;: 24,<br>          &quot;docstring&quot;: &quot;Abstract base class for workflows in a graph-based agent system.&quot;,<br>          &quot;methods&quot;: [<br>            {<br>              &quot;name&quot;: &quot;stop&quot;,<br>              &quot;args&quot;: [<br>                &quot;self&quot;<br>              ],<br>              &quot;docstring&quot;: &quot;Stop the workflow execution. This method can be called by an assistant to stop the workflow during execution.&quot;,<br>              &quot;is_async&quot;: false,<br>              &quot;line_number&quot;: 36<br>            }, ...</pre><p>Example function calls to retrieve resources from the index:</p><pre># multiple function calls<br>get_graphite_resource(grafi_workflows_impl_event_driven_workflow, include_source_code=true)<br>get_graphite_resource(grafi_nodes_node, include_source_code=true)<br>get_graphite_resource(grafi_tools_function_calls_impl_google_search_tool, include_source_code=true)</pre><h4>Code generation and validation</h4><p>Once the necessary resources are retrieved, the real work begins. The code_generation_node is responsible for generating all the required files for the demo project, including README.md, main.py, and requirements.txt.</p><p>Example response for code_generation_node:</p><pre>{<br>  &quot;generated_files&quot;: [<br>    {<br>      &quot;filename&quot;: &quot;main.py&quot;,<br>      &quot;content&quot;: &quot;ACTUAL PYTHON CODE CONTENT HERE&quot;<br>    },<br>    {<br>      &quot;filename&quot;: &quot;requirements.txt&quot;,<br>      &quot;content&quot;: &quot;ACTUAL REQUIREMENTS LIST HERE&quot;<br>    },<br>    {<br>      &quot;filename&quot;: &quot;README.md&quot;,<br>      &quot;content&quot;: &quot;ACTUAL MARKDOWN README CONTENT HERE&quot;<br>    }<br>  ],<br>  &quot;explanation&quot;: &quot;how the code works and what it does&quot;,<br>  &quot;components_used&quot;: [&quot;list&quot;, &quot;of&quot;, &quot;Graphite&quot;, &quot;components&quot;, &quot;used&quot;]<br>}</pre><p>After generating the required code, we will proceed to the code_validation_node, with the intention of ensuring the project is correctly generated. We will go through various steps, including syntax validation, dependencies installation validation, import validation, and code execution validation. All of these checks are performed by a custom code_validation_tool integrated with the code_validation_tool_node</p><p>If any validation test fails, the workflow loops back code_generation_node via the code_regeneration_topic to regenerate the flawed code. This cycle continues until the project passes all validation tests, ensuring a fully functional and error-free demo.</p><p>Example response from code_validation_tool_node:</p><pre>{<br>  &quot;validation_passed&quot;: true/false,<br>  &quot;error&quot;: &quot;Error message if validation failed&quot;,<br>  &quot;generated_files&quot;: &quot;Original files_json for downstream nodes&quot;,<br>  &quot;project_description&quot;: &quot;Project description for context&quot;,<br>  &quot;details&quot;: {<br>    &quot;project_name&quot;: &quot;demo_project&quot;,<br>    &quot;validation_id&quot;: &quot;abc123&quot;,<br>    &quot;main_file&quot;: &quot;main.py&quot;,<br>    &quot;tests_run&quot;: [&quot;syntax&quot;, &quot;dependencies&quot;, &quot;imports&quot;, &quot;execution&quot;],<br>    &quot;all_passed&quot;: true/false,<br>    &quot;details&quot;: {<br>      &quot;syntax&quot;: {&quot;passed&quot;: true, &quot;message&quot;: &quot;Syntax validation passed&quot;},<br>      &quot;dependencies&quot;: {&quot;passed&quot;: true, &quot;message&quot;: &quot;Dependencies installed successfully&quot;},<br>      &quot;imports&quot;: {&quot;passed&quot;: true, &quot;message&quot;: &quot;Import validation passed&quot;},<br>      &quot;execution&quot;: {&quot;passed&quot;: true, &quot;message&quot;: &quot;Execution validation passed&quot;}<br>    }<br>  }<br>}</pre><h4>File Shipping</h4><p>Given that we have passed all the validation tests, the workflow reaches its final step: bundling all the generated files and shipping them back to the user. Thefile_node and file_tool_node are responsible for zipping all the project files and storing them on the backend Docker container, and exposing an API endpoint for users to download the whole project as a single zip. This is done through a file_node function tool that I have defined.</p><p>Example response from file_tool_node:</p><pre>{<br>  &quot;success&quot;: true,<br>  &quot;file_id&quot;: &quot;abc123&quot;,<br>  &quot;project_name&quot;: &quot;university_agent&quot;,<br>  &quot;description&quot;: &quot;AI agent for university queries&quot;,<br>  &quot;files_saved&quot;: [&quot;university_agent.py&quot;, &quot;requirements.txt&quot;, &quot;README.md&quot;],<br>  &quot;download_urls&quot;: {<br>    &quot;individual_files&quot;: {<br>      &quot;university_agent.py&quot;: &quot;http://localhost:8000/api/files/abc123/download/university_agent.py&quot;,<br>      &quot;requirements.txt&quot;: &quot;http://localhost:8000/api/files/abc123/download/requirements.txt&quot;,<br>      &quot;README.md&quot;: &quot;http://localhost:8000/api/files/abc123/download/README.md&quot;,<br>      &quot;.env&quot;: &quot;http://localhost:8000/api/files/abc123/download/.env&quot;<br>    },<br>    &quot;zip_all&quot;: &quot;http://localhost:8000/api/files/download/zip/abc123&quot;<br>  },<br>  &quot;expires_in_hours&quot;: 24<br>}</pre><p>The user can also selectively download the individual files they need.</p><h4>Final Response</h4><p>The final results are then structured by the output node and returned to the user.</p><p>Example response from output_node:</p><pre>{<br>  &quot;status&quot;: &quot;completed&quot;,<br>  &quot;summary&quot;: &quot;Description of the generated project and its functionality&quot;,<br>  &quot;generated_code_link&quot;: {<br>    &quot;individual_files&quot;: {<br>      &quot;university_agent.py&quot;: &quot;http://localhost:8000/api/files/abc123/download/university_agent.py&quot;,<br>      &quot;requirements.txt&quot;: &quot;http://localhost:8000/api/files/abc123/download/requirements.txt&quot;,<br>      &quot;README.md&quot;: &quot;http://localhost:8000/api/files/abc123/download/README.md&quot;<br>    },<br>    &quot;zip_all&quot;: &quot;http://localhost:8000/api/files/download/zip/abc123&quot;<br>  },<br>  &quot;setup_guide&quot;: &quot;1. Download the ZIP file\n2. Extract the files\n3. Run: pip install -r requirements.txt\n4. Execute: python [main_file].py&quot;,<br>  &quot;warnings&quot;: [&quot;Files expire in 24 hours&quot;, &quot;Make sure to have Python 3.8+ installed&quot;]<br>}</pre><p>The whole workflow/Agent ensures that the correct Graphite resources are fetched, the code is running, and files can be downloaded by the user.</p><h3>Improvements?</h3><p>Yes! Just like the Smart Contract Assistant, there are several areas where the Workflow Builder can be improved and made more robust:</p><ul><li><strong>Increase the complexity of the generated project</strong> — Every LLM has its own context window limits; therefore, it restricts the complexity of code that a single code generation node can produce. One possible solution is to add multiple code generation nodes working in parallel and then combine their outputs. This would allow the assistant to generate larger and more complex projects for demonstration purposes.</li><li><strong>Improve workflow efficiency</strong> — Currently, generating a complete project can take several minutes. While this is acceptable for internal use or university demos, a more formal or commercial setting requires faster results. Potential improvements include using parallel LLM nodes, introducing asynchronous operations, or optimizing function calls to reduce the overall execution time. etc.</li><li><strong>More robust tools</strong> — The assistant currently relies on 3 tools: the Graphite tool, the File tool, and the Code Validation tool, which should be sufficient for small-scale demos. However, if the app is going to expand or to a broader audiences, more tools can be added to make the app more robust and convenient, other than improving the existent 3 tools, adding more tools would also be a viable option to proceed, examples would be a Demo Execution tool, which allows users to run the demo workflow directly on the frontend chat interface, another feature would be integrating graphical interface, like Grafi-Dev to visualize the demo workflow on the frontend, this could greatly improve the understandablility of the generated demo.</li></ul><h3>What did I learn?</h3><p>As a recent Computer Science graduate, before starting this internship, I had absolutely no idea what AI agents are, nor how to set up an MCP server, nor where to define prompt templates for LLMs. This internship has been an incredible learning journey, exposing me to the <strong>full lifecycle of building agentic applications</strong>. I learned how to design modular workflows, structure nodes, and connect them to external tools and APIs. I also gained hands-on experience with prompt engineering, orchestrating complex LLM interactions, and debugging multi-step workflows. Beyond the technical skills, I developed a deeper understanding of how AI agents can be applied in real-world scenarios, from automating smart contract deployment to generating fully functional project demos. This experience has significantly expanded my knowledge and confidence in building sophisticated AI-driven applications.</p><p>Here are some important tips/ideas that I have learnt during the internship when building agentic applications or even normal projects:</p><ul><li><strong>Always use structured output for LLM nodes</strong> — I learned that having a clearly defined output format for each node is crucial. Structured outputs reduce hallucinations, make debugging easier, and ensure that downstream nodes can reliably process the results. For example, instead of returning free-form text from an LLM, defining a JSON schema or specific fields allows the agent to act consistently and predictably. Especially for Graphite, data can be sent to topics based on whether the field(s) meet the condition of the topic</li><li><strong>Pay attention to prompt engineering</strong> — The quality of an LLM’s output depends heavily on how the prompts are written. I discovered that carefully crafting prompts, providing context, examples, and clear instructions, can dramatically improve performance. Prompt engineering isn’t just about asking questions — it’s about anticipating edge cases, handling unexpected responses, and guiding the LLM to produce actionable results.</li><li><strong>Utilize coding assistants</strong> — Before the internship, I only used web-based LLMs to ask questions, pasting code whenever there were bugs, or to ask for suggestions. Now I have discovered coding assistants, namely Claude Code, and it was a game-changer for me. Claude Code not only helped me write code faster, but it also allowed me to focus on higher-level design and logic rather than getting bogged down in boilerplate code. Just remember, don’t overly rely on these coding assistants; make sure you truly understand what the code assistant suggests and have your own reasoning before applying the changes.</li><li><strong>Ask questions and seek clarification</strong> — During my journey in building the projects, I have encountered several issues and have caused a lot of confusion. Sometimes, you might have just encountered a bug or issues that are actually from the software or framework itself, but not by your own. Luckily, all people from Binome are happy and willing to help me. Therefore, when you are stuck on an issue for quite a bit of time and have not found a solution, speak up to colleagues, mentors, or teammates. I am sure that there is always someone that are willing to help you out.</li></ul><h3>What’s next for me?</h3><p>The past three months have been an incredibly exciting and rewarding journey. During my internship at Binome, I had the chance to work alongside talented individuals who are good at presenting, developing, and designing. Collaborating with such a team has been both inspiring and educational, and I feel fortunate to have had the opportunity to contribute to meaningful projects.</p><p>Looking ahead, I’m excited to continue working closely with Binome. I hope to take on more challenging and innovative projects, further exploring the potential of AI agents and workflow automation. My goal is to apply the skills and insights I’ve gained during this internship to build tools that are not only technically robust but also have real-world impact, making complex technologies more accessible to users and developers alike. This experience has reinforced my passion for combining AI, software development, and problem-solving, and I look forward to what the next chapter brings.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=7b655c03d3a6" width="1" height="1" alt=""><hr><p><a href="https://medium.com/binome/developing-ai-powered-assistants-with-graphite-lessons-from-my-internship-7b655c03d3a6">Developing AI-Powered Assistants with Graphite: Lessons from My Internship</a> was originally published in <a href="https://medium.com/binome">Binome</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Another MCP Servers Tutorial… Internal Mechanics]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://medium.com/binome/mcp-servers-internal-mechanics-37dba35daab0?source=rss----35517879563d---4"><img src="https://cdn-images-1.medium.com/max/700/0*nhw7wXou1eF-koGI.png" width="700"></a></p><p class="medium-feed-snippet">We in the Binome Team have been working on MCP servers recently and after spending a fair amount of time trying to figure out how they&#x2026;</p><p class="medium-feed-link"><a href="https://medium.com/binome/mcp-servers-internal-mechanics-37dba35daab0?source=rss----35517879563d---4">Continue reading on Binome »</a></p></div>]]></description>
            <link>https://medium.com/binome/mcp-servers-internal-mechanics-37dba35daab0?source=rss----35517879563d---4</link>
            <guid isPermaLink="false">https://medium.com/p/37dba35daab0</guid>
            <category><![CDATA[llm]]></category>
            <category><![CDATA[genai]]></category>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[agents]]></category>
            <category><![CDATA[mcp-server]]></category>
            <dc:creator><![CDATA[Daniel Tapia]]></dc:creator>
            <pubDate>Thu, 12 Jun 2025 16:14:58 GMT</pubDate>
            <atom:updated>2025-06-12T16:30:08.436Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[Graphite in Action: A Demo of the Property Search Agent]]></title>
            <link>https://medium.com/binome/graphite-in-action-a-demo-of-the-property-search-agent-e1d953d566fe?source=rss----35517879563d---4</link>
            <guid isPermaLink="false">https://medium.com/p/e1d953d566fe</guid>
            <category><![CDATA[agentic-workflow]]></category>
            <category><![CDATA[event-driven-architecture]]></category>
            <category><![CDATA[llm-applications]]></category>
            <category><![CDATA[ai-agent-application]]></category>
            <category><![CDATA[ai-agent]]></category>
            <dc:creator><![CDATA[Eisha Sultana]]></dc:creator>
            <pubDate>Tue, 20 May 2025 20:35:55 GMT</pubDate>
            <atom:updated>2025-05-20T21:40:12.180Z</atom:updated>
            <content:encoded><![CDATA[<p>Nowadays, AI Agents have become increasingly powerful. They can automatically write code, surf the internet, and even tidy your PC. However, building AI agents that understand your domain, follow regulated workflow and make decisions based on domain-specific knowledge has still been challenging. With Graphite, it could be much easier.</p><p><a href="https://github.com/binome-dev/graphite">GitHub - binome-dev/graphite: Graphite Agentic Framework by Binome Technologies</a></p><p>If you’ve been following our updates regularly, you may have seen the launch of Graphite — an exciting new AI agent framework.</p><p><a href="https://medium.com/binome/introduction-to-graphite-an-event-driven-ai-agent-framework-540478130cd2">Introduction to Graphite — An Event Driven AI Agent Framework</a></p><p>Graphite is our open-source framework designed to help you build a reliable, flexible and observable AI assistant that can be customized to understand your domain. Whether it’s finance, healthcare, real estate or something totally niche, Graphite empowers building domain specific AI agents that know what they’re doing.</p><p>Next, we’ll walk through a simple — yet highly extensible — example of an AI-driven real-estate consultation.</p><h3>Graphite in Action — KYC Agent for Property Search</h3><p>We began with a classic “Know Your Client” (KYC) pattern — a foundational workflow whenever trust, compliance, and personalized service overlap. In the Graphite repo, we shared a basic<a href="https://github.com/binome-dev/graphite/blob/main/examples/hith_assistant/kyc_assistant_example.py"> KYC example</a> but now we’re taking it a step further with a real-world use case: a property search assistant. This demo leverages Graphite’s powerful event driven workflow to manage human-in-the-loop interactions.</p><p>In real estate, truly knowing your client is the cornerstone of trust , and the key to offering advice that fits them perfectly. Before suggesting any properties, a skilled agent pauses to understand who the client is, what they’re looking for, how much they can spend, and which features are “must-haves” versus “nice-to-haves.”</p><p>A naïve, one-size-fits-all bot fires off blunt queries: <em>“Budget?” “Beds?” “Post code?”</em> — collecting data but not really helping the client. If client knows all the above, they can just search online. A domain-specific agent does the opposite:</p><ol><li><strong>Warm introduction</strong> — Welcomes the prospect and sets expectations.</li><li><strong>Contextual probing</strong> — Surfaces lifestyle goals (“family size,” “walkable nightlife”) rather than just numeric filters.</li><li><strong>Progressive disclosure</strong> — Reveals options in stages, validating assumptions and updating search parameters on the fly.</li></ol><p>By encoding that consultative flow — <em>greeting → rapport → nuanced discovery → tailored recommendations</em> — Graphite demonstrates how specialized agents outperform generic chatbots. Domain knowledge isn’t a nice-to-have; it’s the difference between “Here are 500 listings” and “Here are the three homes that feel made for you.”</p><p>We’ve taken this flow and implemented it using Graphite. Below, you’ll see how each part of the agent’s thought process translates into modular components all working together to make smarter decisions faster.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/838/1*pc7SGYBPREFm-s0MSEnJ6w.png" /></figure><h3>How the Property Search Agent Works with Graphite</h3><p>Graphite’s core strength lies in its <strong>modular, event-driven architecture</strong>. Think of it as a well-rehearsed dance: every step emits an event, and every event triggers the next move. Because each event is logged, you can always trace how the agent reached its decisions.</p><h4><strong>User Interaction</strong></h4><p>The conversation starts with a free-form prompt such as:<br>“<em>Hello, I want to find a property in London.” </em>From that moment, <strong>PropertyAssistant</strong>—Graphite’s high-level assistant for this workflow—takes over. The workflow owns the entire life-cycle: routing messages, storing context, and emitting the events that drive the downstream nodes.</p><h4>Build User Profile</h4><p>This step Powered by a LLM Node — <strong>User Profile Builder Node,</strong> it build the user profile for property recommendation, the data points are:</p><ol><li><em>location (city, neighbourhood)</em></li><li><em>profession &amp; salary band</em></li><li><em>household size (solo, couple, family)</em></li><li><em>lifestyle needs (home office, schools, pets, nightlife)</em></li></ol><p>This node will decide if all the required information is gathered, and produce either “Finished” or “Missing &lt;data point&gt;” to <em>user_profile_builder_result_topic</em></p><h4>Decide Next Step</h4><p>Given the result of <strong>User Profile Builder Node</strong>, the <strong>Action Node</strong> inspects the content and chooses one of two paths:</p><ol><li><em>Enough data? -&gt; </em>route to the <strong>Summary Node</strong><em>.</em></li><li><em>Missing data? -&gt; </em>invoke the <strong>Human Request Function Node</strong> with a concise follow-up question (“What a comfortable monthly payment?”).</li></ol><h4>Human In The Loop (HITL)</h4><p>When the workflow needs more detail, the <strong>Human Request Function Node</strong> publishes the follow-up question (generated by the <strong>Action Node</strong>) to the system reserved topic <em>human_request_topic</em>. The Assistant subscribes to that topic, shows the question to the user, and routes the user’s reply to the next node for parsing.</p><h4>Observe User Reply</h4><p>The <strong>Observation Node</strong>, powered by an LLM, parses the user’s latest reply, converts key details into structured facts — e.g., <em>budget ≈ £2 200 pcm</em>, <em>needs home office</em> — and passes them to the <strong>User Profile Builder Node</strong>. The updated profile re-enters the loop, ready for the next <strong>Action Node </strong>decision.</p><h4><strong>Final Response</strong></h4><p>With a full profile in hand, the <strong>Summary Node</strong> (LLM Node) will renders a concise user preferred property snapshot (“3 bedrooms, price up to 400,000 , West of London”). And generates initial property recommendations from the LLM’s internal knowledge.</p><p>Because the user’s requirements are stored as a <strong>fully structured snapshot</strong>, wiring in a live property API (Rightmove, Zoopla, etc.) is almost effortless. The agent serialises the profile — <strong>“a 3-bed flat in West London, house price up to 400,000, near good primary schools.”</strong> to a function call parameters like</p><pre>{<br> &quot;type&quot;: &quot;flat&quot;,<br> &quot;beds&quot;: 3,<br> &quot;area&quot;: &quot;West London&quot;,<br> &quot;maxPrice&quot;: 400000,<br> &quot;filters&quot;: [&quot;near_primary_school&quot;]<br>}</pre><p>and call a search API. That crisp contract between profile and API is what makes the system plug-and-play and instantly extensible.</p><h3>Example</h3><p>Let’s walk through a real interaction to show how the agent works in practice. From the very first message, the assistant takes on a warm, conversational tone:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*zlSqX2liAheSArGs-wo5HQ.png" /></figure><p>Notice how instead of asking for budget or number of rooms directly, the assistant collects contextual signals — like profession and who will live there — to infer those details.</p><p>As the assistant gathers information, it doesn’t just ask questions, it’s building a profile in the background. Rather than asking users to manually fill out a form, the assistant infers details like:</p><ul><li><strong>Who will live there</strong> → determines space/room needs.</li><li><strong>neighbourhoods</strong> <strong>and location</strong> -&gt; where you may love to find the new home.</li><li><strong>Job, monthly payment an saving</strong>→ estimate budget.</li></ul><p>Based on the user provided information, agent get the summary of the recommendation, such as location, number of rooms, and budget. And the agent also provide some recommended places (based on the knowledge of LLM model).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*yWqYoXPZrJFi_MV1PGy12A.png" /></figure><h3>What’s Next — Smarter, Deeper, and Truly Live</h3><p>Today the agent builds a profile from your chat alone, and based on LLM, which could cause hallucination (like above recommended properties). Next, we’re expanding its reach:</p><ul><li><strong>Real-time data sources</strong> — On demand, the agent will query live feeds for sales trends, neighbourhood crime stats, school-performance scores, and commute times, enriching its reasoning with fresh, objective facts.</li><li><strong>Tool-aware reasoning</strong> — Instead of relying solely on the LLM’s memory, the workflow will call specialised tools (maps APIs, valuation models, census datasets) whenever a question requires them, then fuse those results back into the dialogue.</li><li><strong>End-to-end property search</strong> — We’re wiring in production APIs from Rightmove, Zoopla, and others, so the assistant can turn your profile into a real query, fetch current listings, and return book-ready links.</li><li><strong>Hyper-personal ranking</strong> — Listings will be scored against your exact constraints — budget, commute, child-friendly amenities — so the first page of results already feels “just right.”</li></ul><p>With live data, external tools, and genuine listing feeds in the loop, the assistant moves from prototype to practical house-hunting partner.</p><h3>Conclusion</h3><p>We show the example built by Graphite, which can turn the messy, moving parts of an AI assistant into clean, swappable blocks. To highlight</p><ul><li><strong>Event-driven and modular</strong> — every node is independent but coordinated, so adding RAG, a human-review step, or a brand-new API is literally plug-and-play.</li><li><strong>Transparent by default</strong> — every event is logged and replayable, making debugging and compliance painless.</li><li><strong>Built to scale</strong> — prototype on a weekend, ship to production on Monday without rewriting the workflow.</li></ul><p>The Property Search Agent is just one proof-point. Any domain — support, finance, healthcare — can get the same rock-solid orchestration and observability.</p><p>AI is moving fast. Graphite helps you move faster <strong>without breaking things</strong>. Grab the <a href="https://github.com/binome-dev/graphite">repo</a>, wire up your own nodes, and show us what you build — because the next great assistant won’t replace humans; it’ll work right alongside us.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e1d953d566fe" width="1" height="1" alt=""><hr><p><a href="https://medium.com/binome/graphite-in-action-a-demo-of-the-property-search-agent-e1d953d566fe">Graphite in Action: A Demo of the Property Search Agent</a> was originally published in <a href="https://medium.com/binome">Binome</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Graphite for AI Agents: Event-Driven Design Made Simple]]></title>
            <link>https://medium.com/binome/graphite-for-ai-agents-event-driven-design-made-simple-ede23733b8ef?source=rss----35517879563d---4</link>
            <guid isPermaLink="false">https://medium.com/p/ede23733b8ef</guid>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[python]]></category>
            <category><![CDATA[agentic-workflow]]></category>
            <category><![CDATA[pub-sub]]></category>
            <dc:creator><![CDATA[Yuriy Mud]]></dc:creator>
            <pubDate>Fri, 16 May 2025 21:29:00 GMT</pubDate>
            <atom:updated>2025-05-16T21:29:00.174Z</atom:updated>
            <content:encoded><![CDATA[<h3><strong>Introduction:</strong></h3><p>In the world of AI development, AI agents are becoming an essential part of process automation. However, very often building these agents feels very unintuitive and frustrating to optimize, as they are rigid and hard to debug. Popular agent frameworks like LangChain often rely on deeply nested chains, implicit state, and runtime patching via callbacks making them very difficult to work with, especially when dealing with multi-step tool use or unexpected outputs from an LLM(<a href="https://shashankguda.medium.com/challenges-criticisms-of-langchain-b26afcef94e7">source</a>).In this article I would like to share my personal experience of using Graphite, a new agent development framework that embraces simplicity, event-driven logic and developer transparency.</p><p><a href="https://github.com/binome-dev/graphite">GitHub - binome-dev/graphite: Graphite Agentic Framework by Binome Technologies</a></p><p><strong>Why Graphite you might ask?</strong></p><p>At its core, Graphite utilizes a <a href="https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern">publish-subscribe</a>(Pub-sub) system which allows user to use the following features:</p><ul><li><strong>Loose coupling:</strong> In the Graphite framework, the Nodes subscribe to topic and publish results. This mean you can make any changes to your agent pipeline without accidently breaking the rest of it. Nodes in Graphite subscribe to and publish messages via named topics. That means you can swap out a node, add new behaviors, or reorder flows without touching the rest of the workflow.</li><li><strong>Event-driven workflow:</strong> Instead of a strict hard-coded logic embedded in the agent, Graphite allows you to modify the agent’s behaviour based on specific events. Think of it as having a conversation, each node listens, thinks, responds and then passes the information naturally instead of strict procedural logic. This type of architecture encourages active thinking in events, rather than imperative chains.</li><li><strong>Observability:</strong> Graphite comes with built-in support for Phoenix, allowing you to trace your assistant’s full logic out of the box, the user will be able to trace the assistant’s full logic, meaning you will be able to see which node has ran, what tools have been used and what was published. This allows you to debug your code easily as you will immediately see what has gone wrong rather than blind trial and error.</li></ul><p>For more detailed introduction, please find out in this introduction.</p><p><a href="https://medium.com/binome/introduction-to-graphite-an-event-driven-ai-agent-framework-540478130cd2">Introduction to Graphite — An Event Driven AI Agent Framework</a></p><p>To try out the framework, I decided to build an AI agent that reads event details from an image (like a flyer) and adds them directly to your Google Calendar. If any information is missing like the title or start time, the assistant automatically follows up with the user to clarify before continuing.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*9orKGfUxMyeCWDcJ1GgTLw.png" /></figure><p>This was a strong use case for Graphite: it involved multiple decision points, human-in-the-loop feedback, a need for both language understanding and function execution, and integration with external tools and APIs.</p><h3>Agent Design with Graphite</h3><p>Here is my approach towards designing the workflow of the agent</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*nm50Hqx6RB5hDDWt3F5XWA.png" /></figure><ul><li><strong>VisionNode:</strong> this LLM node will be acting as an entry point of the agent’s workflow. It listens for when an image, typically a base64-encoded string is being uploaded to the agent, and then uses language models to extracts key event details such as: title, date, time and location. It doesn’t make any decisions, its sole purpose is to read the image and pull key information which will then be passed onto the next node for evaluation.</li><li><strong>ActionNode:</strong> this LLM node responsible for evaluating extracted event details from <strong>VisionNode</strong>. It doesn’t reprocess the image but instead is utilising language models to check whether the extracted information is sufficient, based on criteria provided in the system prompt. If the details meet the criteria, the data is passed on to the <strong>CalendarNode</strong> to add the event. If something is missing, it’s sent to <strong>HumanRequestNode</strong> to request additional input from the user.</li><li><strong>HumanRequestNode: </strong>This function node listens on <em>hitl_call_topic</em> and invokes the <em>ask_user</em> tool, which is a function defined by the user. Rather than composing messages with the language model, this tool emits structured function calls that can later be rendered into natural user-facing prompts. Once the tool call is issued, the request is passed back to the user via the <em>human_request_topic</em>.</li><li><strong>CalendarNode: </strong>Once all the required information is present, this tool node will be triggered. This node - similarly to <strong>HumanRequestNode</strong> — doesn’t utilise a language model, but instead uses a predefined function that integrates with Google Calendar API. It operates using a service account, authorized via a JSON key file, which allows it to create calendar entries on behalf of the user without requiring manual login. Once the event is successfully posted, it passes the result to the next node for confirmation.</li><li><strong>ConfirmationNode:</strong> After the event has been successfully posted on the calendar, this LLM node will be triggered. It will be using a language model to craft a final confirmation message for the user, summarizing the event that was added. This message includes the key event details, like title, date, and time, and signals that the assistant has completed the task. The output is then published to the user-facing channel to close the loop.</li></ul><h3><strong>Graphite Agent Code Breakdown</strong></h3><p>In this section, we’ll walk through the code in three parts: <strong>Topics</strong>, <strong>Nodes</strong>, and finally the <strong>Assistant</strong>, which serves as the agent’s interface.</p><h4><strong>Topics</strong></h4><p>Before diving into nodes, let’s revisit how Graphite uses topics. These are named communication channels that connect the nodes. Each topic can also have a condition attached, which lets you implement decision logic declaratively without putting that logic inside the node itself.</p><p>To initiate the workflow, we need a root topic the first sort of communication channel between the user and the agent. In this example as can be seen on the image, that’s the <em>agent_input_topic</em>.</p><pre>from grafi.common.topics.topic import Topic, agent_input_topic</pre><p>When the user uploads an image (like a flyer), the image is published to this topic. The <strong>VisionNode</strong> subscribes to <em>agent_input_topic</em>, making it the first node to be triggered in the pipeline. This node is responsible for extracting event details from the image, and its output then flows through the rest of the system via downstream topics.</p><p>Now let’s look at how we define a topic that only activates if event information is incomplete and the agent needs to ask the user for more details:</p><pre>hitl_call_topic = Topic(<br>    name=&quot;hitl_call_topic&quot;,<br>    condition=lambda msgs: msgs<br>    and msgs[-1].tool_calls<br>    and msgs[-1].tool_calls[0].function.name == &quot;ask_user&quot;,<br>)</pre><p>This condition will check if the last message published from the agent to the topic was a call to trigger the ask_user. This is done by using:</p><ul><li>msgs[-1] which will check the last message posted</li><li>msgs[-1].tool_calls checks if there was any function call in the latest message</li><li>tool_calls[0].function.name == “ask_user” will check whether the function that is being called is specifically ask_user.</li></ul><p>In Graphite, the condition parameter of a topic simply expects a boolean expression: something that returns True or False.</p><p>Another core topic is <em>human_request_topic</em>, which acts as the return path for user responses during human-in-the-loop interactions. This topic is used when the assistant needs to ask the user for missing information, and the user’s reply should re-enter the workflow.</p><p>It’s provided directly by the Graphite framework and can be imported like this:</p><pre>from grafi.common.topics.human_request_topic import human_request_topic</pre><p>If not conditions need to be met, the topic will be defined the following way:</p><pre>event_extracted_topic = Topic(name=&quot;event_extracted_topic&quot;)</pre><h4><strong>Nodes</strong></h4><p>Now let’s walk through the key parts of the Node design to see how it looks in practice, starting with the <strong>VisionNode</strong>.</p><pre>vision_node = (<br>     LLMNode.Builder()<br>     .name(&quot;VisionNode&quot;)<br>     .subscribe(agent_input_topic)<br>     .command(<br>      LLMResponseCommand.Builder()<br>        .llm(<br>             OpenAITool.Builder()<br>             .name(&quot;VisionLLM&quot;)<br>             .api_key(self.api_key)<br>             .model(self.model) <br>             .system_message(self.event_extraction_system_message)  <br>             .build()<br>         )<br>         .build()<br>     )<br>     .publish_to(event_extracted_topic)<br>     .build()<br>    )</pre><p>Let’s focus a bit more on the important parts of the code and what exactly happens there:</p><ol><li><strong>.Builder()</strong>: Each class (like LLMNode, LLMResponseCommand, or OpenAITool) uses a Builder pattern to let you define properties step by step. This keeps the code clean and avoids one giant constructor with too many arguments. You configure the object using chained methods like .name(), .model(), etc., and finally call .build() to create the actual instance.</li><li><strong>.subscribe(agent_input_topic)</strong>: This tells the node where to listen for incoming messages. In this case, it listens on <em>agent_input_topic</em>, which is the root topic triggered when the user uploads an image. Once a message is published to this topic, <strong>VisionNode</strong> gets activated.</li><li><strong>.command(…)</strong>: This defines what the node actually does when triggered. For LLM-based nodes like this one, it runs an LLM call using the configuration inside LLMResponseCommand. This includes the model, system message, and API key all encapsulated in the nested OpenAITool.Builder().</li><li><strong>.publish_to(event_extracted_topic)</strong>: Once the command executes, the node publishes its result to the next topic in the pipeline — here, it’s <em>event_extracted_topic</em>, which triggers the next node.</li><li><strong>.build</strong>: After you’ve set all the necessary configuration via the builder methods, calling .build() finalizes and constructs the actual object. It ensures all internal components are initialized and ready for runtime use. This makes your agent logic more maintainable and flexible to extend later.</li></ol><p>With the output of that node now published to <em>event_extracted_topic</em>, the next step in the workflow is the <strong>ActionNode</strong>, which subscribes to that topic.</p><pre>action_node = (<br>    LLMNode.Builder()<br>    .name(&quot;ActionNode&quot;)<br>    .subscribe(<br>        SubscriptionBuilder()<br>        .subscribed_to(event_extracted_topic)<br>        .or_()<br>        .subscribed_to(human_request_topic)<br>        .build()<br>    )<br>    .command(<br>        LLMResponseCommand.Builder()<br>        .llm(<br>            OpenAITool.Builder()<br>            .name(&quot;ActionLLM&quot;)<br>            .api_key(self.api_key)<br>            .model(self.model)<br>            .system_message(self.action_llm_system_message)<br>            .build()<br>        )<br>        .build()<br>    )<br>    .publish_to(hitl_call_topic)<br>    .publish_to(complete_info_topic)<br>    .build()<br>)</pre><p>You might notice that <strong>ActionNode</strong> subscribes to two different topics: <em>event_extracted_topic</em> and <em>human_request_topic</em>. This is done using the SubscriptionBuilder, which lets you combine multiple topic subscriptions in a declarative and readable way:</p><p>This setup means the node will be triggered in two cases:</p><ol><li>When new event data is extracted by the <strong>VisionNode</strong> and published to <em>event_extracted_topic</em>.</li><li>When the user responds to a follow-up question and the message is published back to <em>human_request_topic</em>.</li></ol><p><strong>ActionNode</strong> will either trigger <strong>HumanRequestNode</strong> or <strong>CalendarNode</strong>, both requiring additional functions defined by the user as can be seen below:</p><pre><br>calendar_node = (<br>    LLMFunctionCallNode.Builder()<br>    .name(&quot;CalendarNode&quot;)<br>    .subscribe(complete_info_topic)<br>    .command(<br>        FunctionCallingCommand.Builder()<br>        .function_tool(self.add_event_to_calendar)<br>        .build()<br>    )<br>    .publish_to(calendar_response_topic)<br>    .build()<br>)</pre><p>Where <em>add_event_to_calendar</em> is an additional function defined by the user, in this case it integrates the Google Calendar API allowing the agent to post events on the behalf of the user.</p><p>That’s the nice thing about Graphite: you can see your logic as a series of event reactions.</p><h4><strong>Assistant Setup</strong></h4><p>Finally, here’s how everything comes together in a real application. In this case, I used FastAPI to wrap the assistant into a lightweight web service, exposing endpoints for image uploads and follow-up messages. In Graphite, the assistant is the top-level object that interfaces with users — essentially, the fully assembled agent.</p><p>It’s important to mention that each request is tied to an ExecutionContext object, which carries the a set of ids to manages identifiers across message life cycles (detail can be found <a href="https://github.com/binome-dev/graphite/blob/main/README.md">here</a>). This allows the assistant to maintain memory across messages crucial for asking users for missing info and then following up with additional context.</p><pre>execution_context = ExecutionContext(<br>    conversation_id=req.conversation_id,<br>    execution_id=uuid.uuid4().hex,<br>    assistant_request_id=uuid.uuid4().hex,<br>)</pre><p>The assistant is instantiated once with all the system prompts injected into the correct nodes at build time:</p><pre>assistant = (<br>  ImageToCalendar.Builder()<br>  .api_key(openai_key)<br>  .event_extraction_system_message(event_extraction_system_message)<br>  .action_llm_system_message(action_llm_system_message)<br>  .observation_llm_system_message(observation_llm_system_message)<br>  .summary_llm_system_message(summary_llm_system_message)<br>  .model(&quot;gpt-4o&quot;)<br>  .build()<br>)</pre><p>The list of all system prompts used by the assistant can be seen <a href="https://github.com/binome-dev/graphite-image-ticket-to-calendar/blob/65126421966cef868ebd639cf66fb2bad721b89d/app.py#L37-L115">here</a>.</p><h3>Example</h3><p>Now the assistant is live and ready to use by the user.</p><p>Here’s an example of how it works. If I upload the following image:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/388/1*n07BQmtYCx-RoGUh7YlrgA.jpeg" /></figure><p>I will be able to get the following output from my agent:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*c5YwjJD_YGba4M99AUpa4w.png" /></figure><p>As mentioned previously, Graphite framework integrates beautifully with observe platform like Arize Phoenix, which means every message passed between nodes, every tool call, every function response it’s all traceable.</p><p>To capture the traces, start the phoenix locally using:</p><pre>phoenix serve</pre><p>You can open the Phoenix UI and:</p><ul><li>Click into a conversation trace</li><li>See which node was active and why</li><li>View the exact tool call made and the output it returned</li></ul><p>Here’s what a typical trace looks like:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*HP85AdDhAO9rbyoadPJFXQ.png" /></figure><p>Each block represents a node execution, showing input, output, and any triggered function tools.</p><h3><strong>Final Thoughts</strong></h3><p>Graphite is hands-down the cleanest and easiest to use agent development framework that you will be able to come across. The pub/sub architecture means you’re never locked into one way of doing things — you can react to events, swap out logic, and compose workflows that evolve with your needs. And it’s not just about connecting nodes together. Graphite actually helps you deal with the messy stuff things like working with external APIs, coordinating between LLM calls and function execution, or tracing what happened when something breaks.</p><p>And yes, it’s open source, feel free to check out the GitHub <a href="https://github.com/binome-dev/graphite">repository</a> if you’re curious, suggestions, and improvements are always welcome.</p><p>And here is the link to this specific <a href="https://github.com/binome-dev/graphite-image-ticket-to-calendar">agent</a> if you want to try it yourself!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ede23733b8ef" width="1" height="1" alt=""><hr><p><a href="https://medium.com/binome/graphite-for-ai-agents-event-driven-design-made-simple-ede23733b8ef">Graphite for AI Agents: Event-Driven Design Made Simple</a> was originally published in <a href="https://medium.com/binome">Binome</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Graphite Deep Dive — Event Driven Workflow]]></title>
            <link>https://medium.com/binome/graphite-deep-dive-event-driven-workflow-3fa6a47ffb5f?source=rss----35517879563d---4</link>
            <guid isPermaLink="false">https://medium.com/p/3fa6a47ffb5f</guid>
            <category><![CDATA[ai-agent]]></category>
            <category><![CDATA[agentic-workflow]]></category>
            <category><![CDATA[event-driven-architecture]]></category>
            <category><![CDATA[agent-framework]]></category>
            <dc:creator><![CDATA[Craig Li, Ph.D]]></dc:creator>
            <pubDate>Tue, 06 May 2025 15:37:54 GMT</pubDate>
            <atom:updated>2025-05-06T18:35:34.710Z</atom:updated>
            <content:encoded><![CDATA[<h3>Graphite Deep Dive — Event Driven Workflow</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*pGaMmP0qSoqY2PyGIbE2BA.png" /></figure><p><strong>Graphite </strong>is an <a href="https://github.com/binome-dev/graphite">open-source framework</a> for building <em>domain-specific</em> AI assistants using composable agentic workflows. Please check our post for more detail. In this post we will dive into Graphite event driven architecture.</p><p><a href="https://medium.com/binome/introduction-to-graphite-an-event-driven-ai-agent-framework-540478130cd2">Introduction to Graphite — An Event Driven AI Agent Framework</a></p><p>Event-driven architecture is at the heart of Graphite, empowering flexible, modular workflows that can adapt to complex business requirements. By leveraging publish/subscribe (pub/sub) patterns, Graphite orchestrates node execution in a way that gracefully handles everything from simple linear flows to branching and cyclical dependencies — all while ensuring the correct sequence of messages for large language models (LLMs) and other downstream tasks.</p><h3>The Benefits of Event Driven Architecture</h3><p>In traditional, <em>coupled </em>workflows, tasks often execute in a fixed sequence: when one step finishes, the next begins. By contrast, <strong>event-driven</strong> systems allow any component to “publish” an event, while interested “subscribers” automatically respond. This approach delivers significant advantages:</p><ul><li><strong>Flexibility:</strong> Add or remove nodes and branches without re-engineering the entire flow.</li><li><strong>Scalability:</strong> Efficiently handle complex interactions with multiple data sources, functions or AI services.</li><li><strong>Modularity:</strong> Encapsulate functionality in discrete nodes that each listen for specific events and publish their results.</li></ul><p>To harness these benefits, Graphite uses concept of <em>Topics</em> — queues that store messages in the FIFO pattern. This design offers a clear, auditable trail of each event passing through your workflow, simplifying both debugging and compliance in complex systems.</p><h3>Inside Graphite’s Event Driven Architecture</h3><p>Graphite’s event-driven workflow consists of four primary components:</p><ol><li><strong>Topic</strong>: the message queues</li><li><strong>Node</strong>: the processing unit</li><li><strong>Event</strong>: the records of every state change</li><li><strong>Event Store</strong>: the durable log of all events</li></ol><p>Below is an example showing a node’s execution lifecycle and the sequence of events it generates.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/741/1*jZ6RVKry191pLAJ1luf6bw.png" /></figure><p>Next, we’ll examine the <strong>Topic</strong> and <strong>Node</strong> components — alongside pub and sub events — to illustrate how Graphite’s event-driven workflow operates.</p><h4>Topic</h4><p>Under the hood, each Topic is like a temporary, in-memory event queue that lasts for the duration of a single request, with a map which store the consume offset of each consumers.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/549/1*Xq94Azk2iXwqJGeZBuGtpQ.png" /></figure><p>At the start of each request, Graphite initializes its assistant Topics. Every incoming message—encapsulated as a PublishToTopicEvent—is appended to the appropriate Topic (subject to a customizable publish condition) message queue. A node consumed from a topic will produce ConsumeFromTopicEvent. Under the hood, Graphite’s Topic class maintains, for each consumer node, a map of consumed offsets so it always knows which messages have been processed and which are still pending.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*YfdU2HJ-BFn0ImeBx41HAg.png" /></figure><p>By using the execution_context, topic_name, and offset fields from PublishToTopicEvent and ConsumeFromTopicEvent, Graphite can rebuild Topic of unfinished assistant request by restoring its message queue from the event store. When a workflow must be resumed, Graphite reinstates the Topic with the exact offsets so it picks up precisely where it left off.</p><h4>Node</h4><p>In Graphite, a <strong>node</strong> is an execution unit that subscribes to one or more input (“sub”) topics and publishes results to one or more output (“pub”) topics. Each node encapsulates its subscription logic — the subscribe_to condition—which determines exactly when it should trigger. Here is the code example of the subscribe_to condition</p><pre>SubscriptionBuilder()<br>.subscribed_to(agent_input_topic)<br>.or_()<br>.subscribed_to(human_request_topic)<br>.build()</pre><p>Once triggered, the node:</p><ul><li>Consumes events from its subscribed topics</li><li>Processes those events</li><li>Publishes the resulting events to its output topics</li></ul><p>Those newly published events can then trigger downstream nodes that subscribe to the same topics.</p><p>Below is an illustration of a single node with its corresponding sub- and pub-topics in Graphite’s event-driven workflow. Whenever new events arrive on a topic, the workflow evaluates each subscribing node’s subscribe_to condition; if the condition is satisfied, that node is added to the execution queue.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/787/1*EHQ-o6HYX7uA5BzpE5n2zA.png" /></figure><p>Once a node completes execution, the workflow publishes its response to the downstream topics. Within each Topic object, the topic’s publish condition is evaluated to determine whether the incoming message should be accepted or ignored.</p><p>In the next section, we’ll explore strategies for resolving circular dependencies between nodes, as well as best practices for preserving a complete, queryable history of all messages as they flow through your workflow.</p><h3>Handling History and Circular Flows</h3><p>AI systems, particularly those involving LLMs, often require loop of “reasoning”. For instance, in a ReAct agent workflow, an LLM-based “Thought”, “Action“ and “Observe“ nodes might repeatedly query in a loop (“Reasoning → acting → observing -&gt; reasoning…”) before finalizing an answer.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/626/1*PcA0HJgdpQ3lYen5UmQggQ.png" /></figure><p>This can create loops and branching flows that make it tricky to maintain a coherent memory or message history. Graphite solves this with <strong>time travel</strong> and <strong>time-weighted topology sort.</strong></p><h4>Time Travel</h4><p>Even with multiple loops or cycles, events in Graphite can be “stretched“ as a “<a href="https://en.wikipedia.org/wiki/Directed_acyclic_graph">Directed Acyclic Graph</a> (DAG)“. By tracking each event’s offset and storing them in Topics, Graphite can “time travel” back to reconstruct which messages existed at each step. In the ReAct example above, the node forms a cycle, but events still unfold in that linear sequence. The illustration below shows how it would appear if the <em>Thought </em>node ended its loop on the third iteration.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/684/1*ijctfs1For_vX8TirwQCng.png" /></figure><h4>Time-Weighted Topology Sort</h4><p>For workflows that resemble general graphs (with branches and circular dependencies), Graphite “stretched“ the events into a DAG, and then is able to sort events by combining traditional topological ordering with timestamps. Events with the same number of dependencies (“in-degree”) are ordered by the time their events were published. This ensures the most recent and contextually relevant events are processed in the correct sequence.</p><p>Now let’s look at the weighted topology sort. Suppose you have an events lineage like following:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/783/1*E3oKkkp9DC8jRy-F-4a5bw.png" /></figure><p>The rules of our time weighted topology sort extended the topology sort such that if there are same in-degree events, then we will sort the corresponding event by timestamp of the event. It will guarantee that the events which are close to the current invoked now will always closer. For above the possible sorted for next input will be:</p><p>Event 1 → Event 3 → Event 2 → Event 4 → Event 5</p><h3>Example</h3><p>We will demonstrate the event-driven workflow of our <a href="https://klu.ai/glossary/react-agent-model">ReAct agent</a>. Below is the workflow captured by our in-house debugging tool, which we plan to open-source once testing is complete.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*RPuLXVfqZ9G42ZuICEIJww.png" /></figure><p>After we asked this question: <br>What is the elevation range for the area that the eastern sector of the Colorado orogeny extends into?</p><p>Here is the response:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/902/1*vRErq6ixnPLpYWMVgtkGRQ.png" /></figure><p>Here are all the events generated from the execution.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/928/1*01duS6cNdjdGcIfSpufd0w.png" /></figure><p>We can see it took 1 circle of ReAct to get the answer. Which is</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/961/1*P-DC4PamkDreDPq6w7CS8w.png" /></figure><p>You can also select a node — for example, the <strong>ActionNode</strong>, which will<strong> </strong>displays the corresponding filtered events.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*9qrStpkp1F0BELrItmMxuw.png" /></figure><p>Switching the event display mode lets you view raw events — essential for tracing, debugging, and auditing</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*q-sfZJxZSzeU8F_-WmuORg.png" /></figure><h3>Conclusion</h3><p>Graphite’s event-driven workflow brings a robust, modular design to AI-driven applications. By blending pub/sub patterns, temporary in-memory Topics, time travel, and time-weighted topology sorting, the system tackles the challenges of complex branching, circular dependencies, and historical context retrieval. Whether you’re an AI engineer building cutting-edge tools, a software developer streamlining your application pipeline, or an product owner seeking a forward-looking platform, Graphite’s architecture highlights how thoughtful, event-centric design can unlock next-level capabilities for AI-enabled products.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=3fa6a47ffb5f" width="1" height="1" alt=""><hr><p><a href="https://medium.com/binome/graphite-deep-dive-event-driven-workflow-3fa6a47ffb5f">Graphite Deep Dive — Event Driven Workflow</a> was originally published in <a href="https://medium.com/binome">Binome</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Introduction to Graphite — An Event Driven AI Agent Framework]]></title>
            <link>https://medium.com/binome/introduction-to-graphite-an-event-driven-ai-agent-framework-540478130cd2?source=rss----35517879563d---4</link>
            <guid isPermaLink="false">https://medium.com/p/540478130cd2</guid>
            <category><![CDATA[llm-agent]]></category>
            <category><![CDATA[ai-agent]]></category>
            <category><![CDATA[llm]]></category>
            <category><![CDATA[agentic-workflow]]></category>
            <category><![CDATA[agent-framework]]></category>
            <dc:creator><![CDATA[Craig Li, Ph.D]]></dc:creator>
            <pubDate>Tue, 01 Apr 2025 09:35:34 GMT</pubDate>
            <atom:updated>2025-05-15T19:23:44.730Z</atom:updated>
            <content:encoded><![CDATA[<h3>Introducing Graphite — An Event Driven AI Agent Framework</h3><p><strong>Graphite </strong>is an open-source framework for building <em>domain-specific</em> AI assistants using composable agentic workflows. It offers a highly extensible platform that can be tailored to unique business requirements, enabling organizations to develop custom workflows suited to their specific domains.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*TgWCFvEQNt_ugUec-bts-g.png" /></figure><p>We’ve mentioned this product in our previous posts, and we’re proud to share that we have now open sourced Graphite on <a href="https://github.com/binome-dev/graphite">GitHub</a>! Check it out and give us a Star ⭐ there if you like it!</p><p><a href="https://github.com/binome-dev/graphite">GitHub - binome-dev/graphite: Graphite Agentic Framework by Binome Technologies</a></p><p>You might be wondering, “With AI solutions like ChatGPT, Claude, and various agentic platforms and frameworks already available, why create another one” The short answer: we identified a gap in solving real-world problems with AI tools. Many generic agents — like typical ReAct or CoT agents — struggle with mission-critical tasks where mistakes can be costly.</p><p>Graphite provides a straightforward and controllable framework for building workflows, allowing large language models (LLMs) not just to reason and plan, but to operate within a defined, auditable, and easily restorable workflow. It also includes essential features such as observability, idempotency, and auditability out of the box.</p><p>In this post, we’ll introduce Graphite briefly, from its architecture to its key features, and provide a simple example: using Graphite to build a Know-Your-Client AI assistant.</p><h3>The Architecture: Simple, Powerful, and Composable</h3><p>Graphite is structured into three conceptual layers — <em>Assistants, Nodes,</em> and <em>Tools</em>:</p><ul><li><strong>Assistants</strong> orchestrate the workflow and manage the conversation state.</li><li><strong>Nodes</strong> encapsulate discrete logic, each dedicated to a specific function (e.g., an LLM call or function execution).</li><li><strong>Tools</strong> are pure functions responsible for executing tasks, such as calling an API or running a Python function.</li></ul><p>Additionally, Graphite employs event sourcing pattern to record every state change. Whenever an Assistant, Node, or Tool is invoked, responds, or fails, a corresponding event is generated and stored in the event store.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/682/1*YLy-pPBC_VIcG6je-o6V2g.png" /></figure><p>The command pattern cleanly separates request initiators from executors through well-defined Command objects and handlers. These Commands carry all the necessary context, enabling nodes to invoke tools in a self-contained and straightforward manner.</p><p>The nodes coordinate through a lightweight, Pub/Sub <em>workflow</em> orchestration mechanism.</p><ul><li><strong>Workflow</strong> orchestrates interactions among nodes using a Pub/Sub pattern with in-memory message queuing.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/814/1*BYer6jcr-Xrn_dL6mj77TA.png" /></figure><p>This architecture unlocks <strong>plug-and-play modularity</strong> — you can build workflows like Lego bricks, add or remove nodes, and isolate failures with surgical precision.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*zz6Nf2zyItARB1wZlP530g.png" /></figure><h3>The Features — What Makes Graphite Shine</h3><p>The core design principles we’ve introduced above shows that set Graphite apart from other agent frameworks are:</p><ol><li><strong>A Simple 3-Layer Execution Model</strong><br>Three distinct layers — assistant, node, and tool — manage execution, while a dedicated workflow layer oversees orchestration.</li><li><strong>Pub/Sub Event-Driven Orchestration</strong><br>Communication relies on publishing and subscribing to events, ensuring a decoupled, modular flow of data throughout the system.</li><li><strong>Events as the Single Source of Truth</strong><br>All operational states and transitions are recorded as events, providing a uniform way to track and replay system behavior if needed.</li></ol><p>Combining these elements, Graphite provides a production-grade AI application framework capable of operating reliably at scale, handling failures gracefully, and maintaining user and stakeholder trust. Four essential capabilities form the backbone of this approach:</p><ol><li><strong>Observability</strong><br>Complex AI solutions involve multiple steps, data sources, and models. Graphite’s event-driven architecture, logging, and tracing make it possible to pinpoint bottlenecks or errors in real time, ensuring that each component’s behavior is transparent and measurable.</li><li><strong>Idempotency</strong><br>Asynchronous workflows often require retries when partial failures occur or network conditions fluctuate. Graphite’s design emphasizes idempotent operations, preventing pub/sub data duplication or corruption when calls must be repeated.</li><li><strong>Auditability</strong><br>By treating events as the single source of truth, Graphite automatically logs every state change and decision path. This level of detailed record-keeping is indispensable for users working in regulated sectors or who need full traceability for debugging and compliance.</li><li><strong>Restorability</strong><br>Long-running AI tasks risk substantial rework if they fail mid-execution. In Graphite, checkpoints and event-based playback enable workflows to resume from the precise point of interruption, minimizing downtime and maximizing resource efficiency.</li></ol><p>Together, these capabilities — observability, idempotency, auditability, and restorability — distinguish Graphite as a framework for building robust and trustworthy AI applications. Below is a detailed breakdown of how Graphite implements each feature.</p><h3>Show Your Code — KYC Assistant</h3><p>When designing an AI-based workflow, keep in mind that large language models introduce uncertainty. It’s helpful to follow <a href="https://en.wikipedia.org/wiki/Occam%27s_razor">Occam’s razor principle</a>, which means the simpler the workflow, the better.</p><h4>Design the workflow</h4><p>Suppose you want to create a “Know Your Client (KYC)” assistant for a gym registration process, with human-in-the-loop (HITL) functionality. The user must provide their full name and email address to finish the registration workflow. And if anything is missing, the workflow pauses and asks client for more information. Clearly the real world problem would need more information, but here we just simplify it for this demo.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/601/1*3m_-nuki8tLBX18qFm1GtQ.png" /></figure><h4>Build the assistant</h4><p>Firstly, install Graphite from pip</p><pre>pip install grafi</pre><p>From above workflow graph, we will need add following components:</p><p><strong>7 topics:</strong></p><ul><li>agent input topic (framework provided)</li></ul><pre>from grafi.common.topics.topic import agent_input_topic</pre><ul><li>user info extract topic</li></ul><pre>user_info_extract_topic = Topic(name=&quot;user_info_extract_topic&quot;)</pre><ul><li>human call topic</li></ul><pre>hitl_call_topic = Topic(<br>            name=&quot;hitl_call_topic&quot;,<br>            condition=lambda msgs: msgs[-1].tool_calls[0].function.name<br>            != &quot;register_client&quot;,<br>        )</pre><ul><li>human in the loop topic (framework provided)</li></ul><pre>from grafi.common.topics.human_request_topic import human_request_topic</pre><ul><li>register user topic topic</li></ul><pre>register_user_topic = Topic(<br>            name=&quot;register_user_topic&quot;,<br>            condition=lambda msgs: msgs[-1].tool_calls[0].function.name<br>            == &quot;register_client&quot;,<br>        )</pre><ul><li>register user respond topic</li></ul><pre>register_user_respond_topic = Topic(name=&quot;register_user_respond&quot;)</pre><ul><li>agent output topic (framework provided)</li></ul><pre>from grafi.common.topics.output_topic import agent_output_topic</pre><p>5 nodes:</p><ul><li>LLM Node [User info extract node] to extract name and email from user input</li></ul><pre>user_info_extract_node = (<br>    LLMNode.Builder()<br>    .name(&quot;ThoughtNode&quot;)<br>    .subscribe(<br>        SubscriptionBuilder()<br>        .subscribed_to(agent_input_topic)<br>        .or_()<br>        .subscribed_to(human_request_topic)<br>        .build()<br>    )<br>    .command(<br>        LLMResponseCommand.Builder()<br>        .llm(<br>            OpenAITool.Builder()<br>            .name(&quot;ThoughtLLM&quot;)<br>            .api_key(self.api_key)<br>            .model(self.model)<br>            .system_message(self.user_info_extract_system_message)<br>            .build()<br>        )<br>        .build()<br>    )<br>    .publish_to(user_info_extract_topic)<br>    .build()<br>)</pre><ul><li>LLM Node [action] to create action given extracted information</li></ul><pre>action_node = (<br>    LLMNode.Builder()<br>    .name(&quot;ActionNode&quot;)<br>    .subscribe(user_info_extract_topic)<br>    .command(<br>        LLMResponseCommand.Builder()<br>        .llm(<br>            OpenAITool.Builder()<br>            .name(&quot;ActionLLM&quot;)<br>            .api_key(self.api_key)<br>            .model(self.model)<br>            .system_message(self.action_llm_system_message)<br>            .build()<br>        )<br>        .build()<br>    )<br>    .publish_to(hitl_call_topic)<br>    .publish_to(register_user_topic)<br>    .build()<br>)</pre><ul><li>Function Tool Node [human-in-the-loop] to ask user for missing information if any</li></ul><pre>human_request_function_call_node = (<br>    LLMFunctionCallNode.Builder()<br>    .name(&quot;HumanRequestNode&quot;)<br>    .subscribe(hitl_call_topic)<br>    .command(<br>        FunctionCallingCommand.Builder()<br>        .function_tool(self.hitl_request)<br>        .build()<br>    )<br>    .publish_to(human_request_topic)<br>    .build()<br>)</pre><ul><li>Function Tool Node [register user] to register the client</li></ul><pre>register_user_node = (<br>    LLMFunctionCallNode.Builder()<br>    .name(&quot;FunctionCallRegisterNode&quot;)<br>    .subscribe(register_user_topic)<br>    .command(<br>        FunctionCallingCommand.Builder()<br>        .function_tool(self.register_request)<br>        .build()<br>    )<br>    .publish_to(register_user_respond_topic)<br>    .build()<br>)</pre><ul><li>LLM Node [response to user] to draft the final respond to user</li></ul><pre>user_reply_node = (<br>    LLMNode.Builder()<br>    .name(&quot;LLMResponseToUserNode&quot;)<br>    .subscribe(register_user_respond_topic)<br>    .command(<br>        LLMResponseCommand.Builder()<br>        .llm(<br>            OpenAITool.Builder()<br>            .name(&quot;ResponseToUserLLM&quot;)<br>            .api_key(self.api_key)<br>            .model(self.model)<br>            .system_message(self.summary_llm_system_message)<br>            .build()<br>        )<br>        .build()<br>    )<br>    .publish_to(agent_output_topic)<br>    .build()<br>)</pre><p>And here is the source code — <a href="https://github.com/binome-dev/graphite/blob/main/examples/hith_assistant/kyc_assistant.py">KYC assistant</a></p><p>Then let’s write a simple code to test it:</p><pre>import json<br>import uuid<br><br>from kyc_assistant import KycAssistant<br>from grafi.common.decorators.llm_function import llm_function<br>from grafi.common.models.execution_context import ExecutionContext<br>from grafi.common.models.message import Message<br>from grafi.tools.functions.function_tool import FunctionTool<br><br><br>class ClientInfo(FunctionTool):<br><br>    @llm_function<br>    def request_client_information(self, question_description: str):<br>        &quot;&quot;&quot;<br>        Requests client input for personal information based on a given question description.<br>        &quot;&quot;&quot;<br>        return json.dumps({&quot;question_description&quot;: question_description})<br><br><br>class RegisterClient(FunctionTool):<br><br>    @llm_function<br>    def register_client(self, name: str, email: str):<br>        &quot;&quot;&quot;<br>        Registers a user based on their name and email.<br>        &quot;&quot;&quot;<br>        return f&quot;User {name}, email {email} has been registered.&quot;<br><br><br>user_info_extract_system_message = &quot;&quot;&quot;<br>&quot;You are a strict validator designed to check whether a given input contains a user&#39;s full name and email address. Your task is to analyze the input and determine if both a full name (first and last name) and a valid email address are present.<br><br>### Validation Criteria:<br>- **Full Name**: The input should contain at least two words that resemble a first and last name. Ignore common placeholders (e.g., &#39;John Doe&#39;).<br>- **Email Address**: The input should include a valid email format (e.g., example@domain.com).<br>- **Case Insensitivity**: Email validation should be case insensitive.<br>- **Accuracy**: Avoid false positives by ensuring random text, usernames, or partial names don’t trigger validation.<br>- **Output**: Respond with Valid if both a full name and an email are present, otherwise respond with Invalid. Optionally, provide a reason why the input is invalid.<br><br>### Example Responses:<br>- **Input**: &quot;John Smith, john.smith@email.com&quot; → **Output**: &quot;Valid&quot;<br>- **Input**: &quot;john.smith@email.com&quot; → **Output**: &quot;Invalid - Full name is missing&quot;<br>- **Input**: &quot;John&quot; → **Output**: &quot;Invalid - Full name and email are missing&quot;<br><br>Strictly follow these validation rules and do not assume missing details.&quot;<br>&quot;&quot;&quot;<br><br><br>def get_execution_context():<br>    return ExecutionContext(<br>        conversation_id=&quot;conversation_id&quot;,<br>        execution_id=uuid.uuid4().hex,<br>        assistant_request_id=uuid.uuid4().hex,<br>    )<br><br><br>def test_kyc_assistant():<br>    execution_context = get_execution_context()<br><br>    assistant = (<br>        KycAssistant.Builder()<br>        .name(&quot;KycAssistant&quot;)<br>        .api_key(<br>            &quot;YOUR_OPENAI_API_KEY&quot;<br>        )<br>        .user_info_extract_system_message(user_info_extract_system_message)<br>        .action_llm_system_message(<br>            &quot;Select the most appropriate tool based on the request.&quot;<br>        )<br>        .summary_llm_system_message(<br>            &quot;Response to user with result of registering. You must include &#39;registered&#39; in the response if succeed.&quot;<br>        )<br>        .hitl_request(ClientInfo(name=&quot;request_human_information&quot;))<br>        .register_request(RegisterClient(name=&quot;register_client&quot;))<br>        .build()<br>    )<br><br>    while True:<br>        # Initial User Input<br>        user_input = input(&quot;User: &quot;)<br>        input_data = [Message(role=&quot;user&quot;, content=user_input)]<br><br>        output = assistant.execute(execution_context, input_data)<br><br>        responses = []<br>        for message in output:<br>            try:<br>                content_json = json.loads(message.content)<br>                responses.append(content_json[&quot;question_description&quot;])<br>            except json.JSONDecodeError:<br>                responses.append(message.content)<br><br>        respond_to_user = &quot; and &quot;.join(responses)<br>        print(&quot;Assistant:&quot;, respond_to_user)<br><br>        if &quot;registered&quot; in output[0].content:<br>            break<br><br><br>if __name__ == &quot;__main__&quot;:<br>    test_kyc_assistant()</pre><h4>Test, Observe, Debug, and Improve</h4><p>Graphite uses <a href="https://opentelemetry.io/">OpenTelemetry</a> and Arize’s <a href="https://docs.arize.com/phoenix/reference/open-inference">OpenInference</a> to trace and observe the assistant behaviour.</p><p>To capture the traces locally, we can start <a href="https://phoenix.arize.com/">phoenix</a> locally.</p><pre>phoenix serve</pre><p>Here is an example of the conversation between user and assistant</p><pre>&gt; User: Hi, I&#39;d like to sign up for your gym. Could you help me with the process?<br>&gt; Assistant: Please provide your full name and email address to sign up for the gym.<br>&gt; User: my name is Craig Li, and here is my email: craig@example.com<br>&gt; Assistant: Congratulations, Craig! You are now registered at our gym. If you have any questions or need assistance, feel free to ask!</pre><p>Looks good. But if you push the boundary of the tests, you will hit the error real quick. Like this</p><pre>&gt; User: Hi, how are you today? </pre><p>An error occurs, and when we debug with Phoenix, we can pinpoint its exact location:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*IIfMF6O_doVj5sUW.png" /></figure><p>The issue originates from the Action LLM. Given the input, it should select the appropriate tool, but instead, it generates a plain string response.</p><p>To fix this, we can provide clear instructions: if the user asks a question unrelated to gym registration, the LLM should use the request_client_information tool to respond politely and ask if they need assistance with gym registration.</p><p>so let’s update action LLM system prompt</p><pre>.action_llm_system_message(<br>            &quot;Select the most appropriate tool based on the request. if the user asks a question unrelated to gym registration, the LLM should use the `request_client_information` tool to respond politely and ask if they need assistance with gym registration.&quot;<br>        )</pre><p>and try again</p><pre>&gt; User: Hi, how are you today? <br>&gt; Assistant: I&#39;m here to assist you with gym registration. Could you please provide your full name and email?</pre><p>You’ve now built an agent that can assist with gym registrations! You can find the assistant’s code at this <a href="https://github.com/binome-dev/graphite/blob/main/examples/hith_assistant/kyc_assistant.py">link</a> and an example is available <a href="https://github.com/binome-dev/graphite/blob/main/examples/hith_assistant/kyc_assistant_example.py">here</a>.</p><h3>Summary</h3><p>Graphite is an open-source framework for building domain-specific AI assistants through composable, event-driven workflows. With loosely coupled components — assistants, nodes, tools, and workflows — Graphite enables modular design, where events serve as the single source of truth. Its architecture supports complex logic like LLM calls, function execution, and RAG retrieval, all coordinated via dedicated pub/sub topics and commands. Built-in support for observability, idempotency, auditability, and restorability makes it production-ready, allowing workflows to scale, recover, and adapt to compliance needs. Whether you’re building conversational agents or automated pipelines, Graphite offers a flexible, extensible foundation for real-world AI systems.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=540478130cd2" width="1" height="1" alt=""><hr><p><a href="https://medium.com/binome/introduction-to-graphite-an-event-driven-ai-agent-framework-540478130cd2">Introduction to Graphite — An Event Driven AI Agent Framework</a> was originally published in <a href="https://medium.com/binome">Binome</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Design LLM-Based Agents: Key Principles — Part 2]]></title>
            <link>https://medium.com/binome/design-llm-based-agents-key-principles-part-2-8f4011e54637?source=rss----35517879563d---4</link>
            <guid isPermaLink="false">https://medium.com/p/8f4011e54637</guid>
            <category><![CDATA[binome]]></category>
            <category><![CDATA[llm]]></category>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[agents]]></category>
            <dc:creator><![CDATA[Craig Li, Ph.D]]></dc:creator>
            <pubDate>Tue, 21 Jan 2025 16:48:55 GMT</pubDate>
            <atom:updated>2025-01-21T18:42:34.736Z</atom:updated>
            <content:encoded><![CDATA[<h3>Design LLM-Based Agents: Key Principles — Part 2</h3><p>In this post we will continually discuss <strong>LLM-based AI Agent design patterns</strong>, building on the foundations from <a href="https://medium.com/binome/designing-llm-based-agents-key-principles-part-1-7e8c6fe3ddaf">Designing LLM-Based Agents: Key Principles — Part 1</a> . Previously, we introduced high-level AI Agent design patterns and highlighted the concept of <strong><em>agentic workflows</em></strong>. Now, we will take a closer look at the agentic workflow and demonstrate how to integrate software design patterns to make AI Agents robust, scalable, and observable.</p><p>Before diving into the design patterns, let’s clarify three important terms that will be used throughout this post:</p><ul><li><strong>Agentic AI</strong>: A type of AI that can make decisions and take actions autonomously. It’s designed to adapt to changing environments and events, and can work with limited human supervision. Agentic AI includes complex behaviours like collaboration, problem-solving, and decision-making in unpredictable or open-ended environments. <em>Think of Agentic AI as a estate agency. It is a system to reach out to potential clients, handle a variety of buying and selling scenarios, and generally interact fluidly with the market.</em></li><li><strong>AI Agent</strong>: An AI Agent is an autonomous software entity designed to perceive its environment, reason about it, and take actions to achieve specific goals. <em>Think of an AI Agent as an employee in that estate agency — independently picking up calls, making decisions, and working to produce profit for the agency.</em></li><li><strong>Agentic Workflow</strong>: The Agentic Workflow is the structured process by which AI Agents operate and achieve their goals. <em>It’s akin to the runbook an employee follows at estate agency. For example, when they pick up a call, there’s a procedure to understand the client’s needs before providing a suitable response.</em></li></ul><p>In this post, we will focus on the agentic workflow because it forms the core of a successful AI Agent.</p><h3>The Components of Agentic Workflow and its Design Patterns</h3><p>The workflow orchestrates a set of <strong>task nodes</strong> that respond to incoming requests. From a software engineering standpoint, a <em>good</em> agentic workflow — particularly one that leverages LLMs — should be:</p><ol><li><strong>Observable</strong>: Engineers should be able to see what is happening within the workflow at every step.</li><li><strong>Flexible</strong>: Workflow components should be decoupled, allowing developers to modify or replace them easily.</li><li><strong>Restorable</strong>: Because LLM calls and tool integrations can be expensive or occasionally fail, we want to be able to restore the workflow from the point of failure to save costs and simplify debugging.</li></ol><p>From a software architecture standpoint, three key patterns help meet these requirements:</p><ul><li><strong>Event-Driven Architecture: </strong>The workflow operates on a publish/subscribe (pub/sub) model, ensuring a decoupled structure. This design supports scalability, resilience, and modularity — critical qualities for modern systems.</li><li><strong>Event Sourcing Pattern: </strong>Every decision that a workflow component makes is recorded in an event store. This store forms an immutable log of all events — an invaluable resource for both observability and restorability. It also serves as the “memory” layer for the workflow.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/661/1*LMG8vWOUCfUnxWUsL8HFFg.png" /></figure><ul><li><strong>Command Pattern: </strong>By decoupling orchestration (which determines what needs to be done) from execution (the actual task), the system becomes significantly more reusable, testable, and flexible. For example, an <em>LLM Command</em> encapsulates the invocation of an <em>LLM tool</em>, such as OpenAI, Bedrock, Ollama, or others, based on the provided command context.</li></ul><p>At the heart of event driven architecture is a workflow constructed from <strong>nodes</strong> and <strong>topics</strong>:</p><ul><li><strong>Nodes</strong>: These are the functional units of the system. A node executes a specific <strong>tool</strong> through <strong>command</strong> in response to an event.</li><li><strong>Topics</strong>: These act as communication channels. Nodes produce messages to topics and consume messages from topics, enabling seamless communication.</li></ul><p>Based on the pub/sub event driven architecture, a node publishes a message to a topic upon completing a command. Any node subscribed to that topic consumes the message and, when applicable, executes its corresponding command. This <em>decoupled design</em> forms the foundation for the system’s scalability, resilience, and modularity, enabling seamless communication and robust workflows.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/747/1*s7wLfS0yJE8feBIQon-8Yg.png" /><figcaption>Node 5 subscribes to Topic 2 and Topic 4 using OR logic, meaning it triggers when either Topic 2 or Topic 4 receives new messages. Upon activation, Node 5 executes an LLM Command, invoking the LLM tool. After processing the response from the LLM tool, Node 5 publishes the resulting message to Topic 5.</figcaption></figure><p>Based on all above, here is the workflow design</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*w0lci4jwxtvFx0lx0FUOgA.png" /></figure><h3>Memory — A “Side Effect“ of Event Sourcing Pattern</h3><p>In this post, “memory” refers specifically to how the agent retains the history of its conversation with the caller — distinct from the memory mechanisms typically found in transformer architectures.</p><p>One of the most powerful features of event sourcing is its immutable log of events, which effectively serves as a memory layer. Through this log, nodes can retrieve context-relevant information and maintain a coherent conversation across multiple interactions.:</p><ul><li><strong>Instant Memory (Per Request)</strong>: A node can look up relevant context from events generated within the current request. Each node in the directed graph aggregates the data from all of its ancestor nodes — i.e., any nodes from which it can be reached by following the graph’s edges backward — so that when the node executes, it has the complete set of data from every path leading into it.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/522/1*-WP6aB6kKr3g7wPu0qXlLw.png" /></figure><ul><li><strong>Short-Term Memory (Per Conversation)</strong>: In conversational AI (e.g., chatbots), the system can maintain context by referencing the sequence of events that occurred in the current conversation.</li><li><strong>Long-term memory</strong> (or a knowledge base) can be generated from events too, and it’s another great topic we will address in a future post.</li></ul><p>Note that the event store serves as the source of truth for the agent’s memory. Since each event is recorded at the <em>tool</em> level, an agent can assemble a relevant history of events to feed into an LLM node.</p><p>We see that adopting this design not only accelerates business growth through agile agent development, but also streamlines the engineering process by treating the agent as a standard service (<a href="https://medium.com/binome/multi-agent-as-a-service-a-senior-engineers-overview-4e4a020cde44">Multi-Agent-as-a-Service — A Senior Engineer’s Overview</a>) within a broader software ecosystem. By aligning with an established SDLC (software development life cycle), teams can iterate quickly, integrate new requirements seamlessly, and maintain a robust, scalable solution that meets evolving market demands. And the good news is, we’re already developing this agent framework. Here’s a quick look at what we’ve accomplished so far.</p><h3>Build a ReAct Agent with Binome Agent Framework</h3><p>The <strong>ReAct agent</strong> is a well-known agent design pattern. For a detailed explanation, refer to our post — <a href="https://medium.com/binome/ai-agent-workflow-design-patterns-an-overview-cf9e1f609696">AI Agent Workflow Design Patterns — An Overview</a>. Below is a short code snippet illustrating how you might build a ReAct Agent using Binome Agent Framework:</p><pre># Create workflow builder<br>workflow_builder = EventDrivenWorkflow.Builder().name(<br>    &quot;ReActAssistantWorkflow&quot;<br>)<br><br># Create thought result topic<br>thought_result_topic = Topic(name=&quot;thought_result&quot;)<br># Create observation result topic<br>observation_result_topic = Topic(name=&quot;observation_result&quot;)<br>&quot;&quot;&quot;<br>Build thought node, which either agent_input_topic or observation_result_topic<br>can trigger this node, then publish to thought_result_topic. <br>Given inputs, this node would use OpenAI to provide &quot;thought&quot;.<br>&quot;&quot;&quot;<br>thought_node = (<br>    LLMNode.Builder()<br>    .name(&quot;ThoughtNode&quot;)<br>    .subscribe(<br>        SubscriptionBuilder()<br>        .subscribed_to(agent_input_topic)<br>        .or_()<br>        .subscribed_to(observation_result_topic)<br>        .build()<br>    )<br>    .command(<br>        LLMResponseCommand.Builder()<br>        .llm(<br>            OpenAITool.Builder()<br>            .name(&quot;ThoughtLLMTool&quot;)<br>            .api_key(self.api_key)<br>            .model(self.model)<br>            .system_message(&quot;&quot;&quot;<br>You are an AI assistant tasked with analyzing the user&#39;s question and considering the provided observation to determine the next logical step required to answer the question. <br>Your response should describe what would be the most effective action to take based on the information gathered.<br>If the information is sufficient to answer the question, return the answer with confirmation the answer is ready.<br>&quot;&quot;&quot;)<br>            .build()<br>        )<br>        .build()<br>    )<br>    .publish_to(thought_result_topic)<br>    .build()<br>)<br># Add thougt node to workflow.<br>workflow_builder.node(thougt_node)<br># Create action result topics, each of them has its own publish condition.<br>action_result_search_topic = Topic(<br>    name=&quot;action_search_result&quot;,<br>    condition=lambda msgs: msgs[-1].function_call is not None,<br>)<br>action_result_finish_topic = Topic(<br>    name=&quot;action_finish_result&quot;,<br>    condition=lambda msgs: msgs[-1].content is not None<br>    and msgs[-1].content.strip() != &quot;&quot;,<br>)<br>&quot;&quot;&quot;<br>Create action node, which subscribed to thought_result_topic, and publish to<br>two action result topics. <br>This node uses OpenAI to decide finish the react loop or continue search the <br>website to find the user&#39;s question. <br>&quot;&quot;&quot;<br>action_node = (<br>    LLMNode.Builder()<br>    .name(&quot;ActionNode&quot;)<br>    .subscribe(thought_result_topic)<br>    .command(<br>        LLMResponseCommand.Builder()<br>        .llm(<br>            OpenAITool.Builder()<br>            .name(&quot;ActionLLMTool&quot;)<br>            .api_key(self.api_key)<br>            .model(self.model)<br>            .system_message(&quot;&quot;&quot;<br>You are an AI assistant responsible for executing actions based on a given plan to retrieve information. <br>Specify the appropriate action to take, such as performing a search query or accessing a specific resource, to gather the necessary data.<br>If answer is ready, return **FINISH REACT**. <br>&quot;&quot;&quot;)<br>            .build()<br>        )<br>        .build()<br>    )<br>    .publish_to(action_result_search_topic)<br>    .publish_to(action_result_finish_topic)<br>    .build()<br>)<br># Add action node to workflow<br>workflow_builder.node(action_node)<br># Create search result topic<br>search_function_result_topic = Topic(name=&quot;search_function_result&quot;)<br># Create search function node, which subscribed to the action_result_search_topic<br># This node will call search function<br>search_function_node = (<br>    LLMFunctionCallNode.Builder()<br>    .name(&quot;SearchFunctionNode&quot;)<br>    .subscribe(action_result_search_topic)<br>    .command(<br>        FunctionCallingCommand.Builder().function_tool(self.search_tool).build()<br>    )<br>    .publish_to(search_function_result_topic)<br>    .build()<br>)<br># Add search function node to the workflow. <br># The function information will be regiested to action node.<br>workflow_builder.node(search_function_node)<br>&quot;&quot;&quot;<br>Create observation node, which subscribed to the search function result topic, <br>and publish to observation result topic. <br>This node use OpenAI to generate &quot;observation&quot; from user input, thought -&gt; action results.<br>&quot;&quot;&quot;<br>observation_node = (<br>    LLMNode.Builder()<br>    .name(&quot;ObservationNode&quot;)<br>    .subscribe(search_function_result_topic)<br>    .command(<br>        LLMResponseCommand.Builder()<br>        .llm(<br>            OpenAITool.Builder()<br>            .name(&quot;ObservationLLMTool&quot;)<br>            .api_key(self.api_key)<br>            .model(self.model)<br>            .system_message(&quot;&quot;&quot;<br>You are an AI assistant that records and reports the results obtained from executed actions. <br>After performing an action, provide a clear and concise summary of the findings relevant to the user&#39;s question.<br>&quot;&quot;&quot;)<br>            .build()<br>        )<br>        .build()<br>    )<br>    .publish_to(observation_result_topic)<br>    .build()<br>)<br># Add observation node to workflow<br>workflow_builder.node(observation_node)<br>&quot;&quot;&quot;<br>Create the summaries node, which use OpenAI to summarize all the results from all above<br>This node will produce the message to agent_output_topic, which will be consumed by agent<br>and then respond to user. <br>&quot;&quot;&quot;<br>summaries_node = (<br>    LLMNode.Builder()<br>    .name(&quot;SummariesNode&quot;)<br>    .subscribe(action_result_finish_topic)<br>    .command(<br>        LLMResponseCommand.Builder()<br>        .llm(<br>            OpenAITool.Builder()<br>            .name(&quot;SummariesLLMTool&quot;)<br>            .api_key(self.api_key)<br>            .model(self.model)<br>            .system_message(&quot;&quot;&quot;<br>You are an AI assistant tasked with summarizing the findings from previous observations to provide a clear and accurate answer to the user&#39;s question. <br>Ensure the summary directly addresses the query based on the information gathered.<br>&quot;&quot;&quot;)<br>            .build()<br>        )<br>        .build()<br>    )<br>    .publish_to(agent_output_topic)<br>    .build()<br>)<br><br># Add summaries_node to workflow<br>workflow_builder.node(summaries_node)<br><br># Finally build the workflow<br>workflow = workflow_builder.build()</pre><p>Below is the output workflow visualisation. Orange boxes represent topics, while navy-blue boxes represent nodes.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/878/1*1Tst_2dHizPDqUkz0NYE7g.png" /></figure><p>Observability is crucial for any AI system. Below is an example of how we use <a href="https://phoenix.arize.com/">Phoenix by Arize</a> to monitor real-time metrics and gain insights into our agents. The following screenshots illustrate the input of a “summarise” node, which includes historical data generated by various nodes. In sequence, the data points are: system message, user input, thought node, action node, search tool node, observation node, thought node, action node, and finally, the summarise node.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*OHch6wfYU-wxXw_XZ5FQiQ.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*RaL8sl3LUeoFK16lraJqAA.png" /></figure><p>We hope you enjoy our work from above “glimpse”. And we are excited to announce that we plan to <strong>open-source</strong> our Agent Framework soon. Currently, we are polishing the codebase and adding more documentation, so stay tuned for future updates!</p><h3>Conclusion</h3><p>By combining event-driven architecture, event sourcing, and the command pattern, we create an agentic workflow that is robust, transparent, and easy to extend — key qualities for any modern AI system. This approach not only addresses practical challenges such as observability, scalability, and state restoration, but it also fosters agile development by treating AI Agents as standard services within a larger software ecosystem.</p><p>As we continue to refine and open-source our agent framework, we look forward to sharing more detailed examples, best practices, and real-world applications. Stay tuned for our upcoming posts, where we will dive deeper into advanced topics like long-term memory, conversation strategies, and more.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8f4011e54637" width="1" height="1" alt=""><hr><p><a href="https://medium.com/binome/design-llm-based-agents-key-principles-part-2-8f4011e54637">Design LLM-Based Agents: Key Principles — Part 2</a> was originally published in <a href="https://medium.com/binome">Binome</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[AI Agent Workflow Design Patterns — An Overview]]></title>
            <link>https://medium.com/binome/ai-agent-workflow-design-patterns-an-overview-cf9e1f609696?source=rss----35517879563d---4</link>
            <guid isPermaLink="false">https://medium.com/p/cf9e1f609696</guid>
            <category><![CDATA[agentic-workflow]]></category>
            <category><![CDATA[ai-design]]></category>
            <category><![CDATA[agents]]></category>
            <category><![CDATA[ai-workflow]]></category>
            <dc:creator><![CDATA[Craig Li, Ph.D]]></dc:creator>
            <pubDate>Tue, 10 Dec 2024 16:13:56 GMT</pubDate>
            <atom:updated>2024-12-10T16:13:56.181Z</atom:updated>
            <content:encoded><![CDATA[<h3>AI Agent Workflow Design Patterns — An Overview</h3><p>In our previous post, we introduced <a href="https://medium.com/binome/designing-llm-based-agents-key-principles-part-1-7e8c6fe3ddaf">AI Agent Design Patterns</a>. Now, we are in the process of implementing an agent framework based on that design. At the same time, we are actively researching various workflow design patterns. Special thanks to <a href="https://www.linkedin.com/in/yi-z-a7a4602a2/">Lychee Zhong</a> for compiling these patterns with clear and informative diagrams.</p><p>Before diving deeper, let’s revisit the concept of an LLM-based AI agent. As described by the <a href="https://developer.nvidia.com/blog/introduction-to-llm-agents/">nvidia blog</a>, LLM-powered agents are systems that utilize a large language model (LLM) to reason through problems, create actionable plans, and execute those plans using a set of tools. Simply put, these agents combine advanced reasoning capabilities, memory, and task execution.</p><p>The evolution of LLM-based AI agent workflow design patterns began in late 2022 and continues to evolve and innovate.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*08KTKf0vmwm-pTdvhOcoSg.png" /><figcaption><a href="https://arxiv.org/abs/2210.03629">This paper introduced ReAct Pattern, which published in October 2022</a>. This pattern enhancing the strength of Agents by integrating the use of tools.</figcaption></figure><p>This is one of the earliest papers on LLM Agents. Although it may seem basic by today’s standards, at the time, ChatGPT hadn’t been released yet, and the idea of teaching LLMs to use tools was groundbreaking.</p><p>In the following section, we summarize the patterns we’ve learned and believe will be applicable to real-world business needs.</p><h3>Introduction</h3><p>Currently, there are numerous workflow designs. To summarize them based on their focus, we’ve categorized the workflow design patterns we’ve learnt so far into two groups: <strong>Reflection-focused</strong> and <strong>Planning-focused</strong>.</p><h4><strong>Reflection-focused</strong></h4><p><strong>Reflection</strong> allows agents to learn from experience, improving adaptability and resilience. These agents emphasize introspection and learning from past experiences. They analyze previous actions and outcomes to refine future behaviour. By evaluating their performance, they identify mistakes and successes, allowing for continuous improvement. This reflective process enables the agent to adapt its strategies over time, leading to more effective problem-solving. [<a href="https://blog.langchain.dev/reflection-agents/">LangChain Blog</a>]</p><ul><li><a href="https://github.com/langchain-ai/langgraph/blob/main/docs/docs/tutorials/reflection/reflection.ipynb"><strong>Basic Reflection</strong>: Reflecting and learning from the steps.</a></li><li><a href="https://github.com/langchain-ai/langgraph/blob/main/docs/docs/tutorials/reflexion/reflexion.ipynb"><strong>Reflexion</strong>: Enhancing the next steps of the Agent through reinforcement learning.</a></li><li><a href="https://github.com/langchain-ai/langgraph/blob/main/docs/docs/tutorials/lats/lats.ipynb"><strong>Tree search</strong>: TOT + reinforcement learning-based reflection.</a></li><li><a href="https://arxiv.org/abs/2402.03620"><strong>Self-Discover</strong>: Reasoning within the task.</a></li></ul><h4><strong>Planning-focused</strong></h4><p><strong>Planning</strong> enables agents to approach tasks methodically, increasing efficiency and effectiveness. These agents prioritize the development of structured plans before taking action. They decompose complex tasks into manageable sub-tasks and sequence them logically to achieve specific goals. By formulating detailed plans, these agents can anticipate potential challenges and allocate resources efficiently, resulting in more organized and goal-directed behaviour. [<a href="https://arxiv.org/pdf/2402.02716">https://arxiv.org/pdf/2402.02716</a>]</p><ul><li><a href="https://github.com/langchain-ai/langgraph/blob/main/docs/docs/tutorials/plan-and-execute/plan-and-execute.ipynb"><strong>Plan &amp; Solve</strong>: Plan → Task list → RePlan.</a></li><li><a href="https://github.com/langchain-ai/langgraph/blob/main/docs/docs/tutorials/llm-compiler/LLMCompiler.ipynb"><strong>LLM compiler</strong>: Plan → Action in parallel → Joint execution.</a></li><li><a href="https://github.com/langchain-ai/langgraph/blob/main/docs/docs/tutorials/rewoo/rewoo.ipynb"><strong>REWOO</strong>: Plan (including dependencies) → Action (depends on the previous step).</a></li><li><a href="https://github.com/langchain-ai/langgraph/blob/main/docs/docs/tutorials/storm/storm.ipynb"><strong>Storm</strong>: Search for outline → Search each topic in the outline → Summarize into a long text.</a></li></ul><p>When reviewing these design patterns, we see the workflow as an orchestrator. Each node can represent an LLM task, a function call, and other tasks like Retrieval-Augmented Generation (RAG) task, which we typically treat as another type of function call. This concept is one of the main drivers behind developing our own agent. We’ve designed the workflow as a flexible task orchestrator, allowing developers to create various workflows to tackle different problems.</p><p>In this post, we will explore two specific workflow design patterns: the <em>ReAct pattern</em> and the <em>Plan-Solve pattern</em>. For the remaining patterns, we’ll provide a brief summary to explain their motivation and use cases.</p><h3>ReAct Pattern</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*08KTKf0vmwm-pTdvhOcoSg.png" /></figure><p>The ReAct principle is straightforward and reflects a core aspect of human intelligence: “actions guided by verbal reasoning.” After each action, there’s an internal “Observation” or self-reflection: “What did I just do? Have I achieved my goal?” This enables the agent to retain short-term memory. Before ReAct, reasoning and action were treated as separate processes.</p><p>For example, imagine you ask someone to find a pen on your desk, and you give step-by-step instructions (similar to the Chain-of-Thought prompting strategy):</p><ol><li>First, check the pen holder.</li><li>Then, look in the drawer.</li><li>Lastly, check behind the computer monitor.</li></ol><p>Without ReAct, regardless of where the pen is found, the person would follow all the steps, checking each location (Action).</p><p>With ReAct, the process would look like this:</p><ol><li>Action 1: First, check the pen holder;</li><li>Observation 1: The pen is not in the pen holder, so move to the next step;</li><li>Action 2: Then, check the drawer;</li><li>Observation 2: The pen is in the drawer;</li><li>Action 3: Take the pen from the drawer.</li></ol><h4>ReAct Implementation</h4><p>After reviewing several open-source codes, let’s focus on <a href="https://github.com/samwit/langchain-tutorials/blob/main/agents/YT_Exploring_ReAct_on_Langchain.ipynb">the simplest one</a> for analysis. As you explore it, you’ll notice that, at its essence, all Agent design patterns <em>revolve around translating human thinking and management strategies into structured prompts</em>. These prompts guide the large model to plan, invoke tools for execution, and continuously refine its approach through iteration.</p><p>The code logic is outlined in the diagram below (take a close look):</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*yw_98xQCBzzpuJ0nzLEYDw.jpeg" /></figure><ul><li><strong>Generate the prompt</strong>: First, the predefined ReAct prompt template (formatted as Question -&gt; Thought -&gt; Action -&gt; Observation) is merged with the user’s question. The resulting prompt looks like this.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*xTfOitvz3wL5QAGijgONow.jpeg" /></figure><ul><li><strong>Call the large model to generate Thought + Action</strong>: Next, send the few-shot prompt to the large model. If sent as is, the model will generate Thought, Action, and Observation responses. However, since the Action isn’t fully defined, we prevent the model from generating the Observation by using Stop.Observation, ensuring it stops after Thought and Action.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Mph9P_JDiASY5nq_ZI_Qbw.jpeg" /></figure><ul><li><strong>Calling External Tools</strong>: Once the Action is obtained, the model can call external tools. First, check if the Action is “Finish.” If not, the model converts the Action into an API-compatible format using its function-calling feature. This fine-tunes the model for language-to-API conversion, though not all large models support function calling.</li><li><strong>Generating Observation</strong>: After the API interface returns the result, it is converted into natural language output to generate the Observation. Then the Observation, along with the previously generated Thought and Action, is input back into the model, repeating steps 2 and 3 until the Action is “Finish.”</li><li><strong>Final Output</strong>: The final Observation is converted into natural language and output to the user.</li></ul><p>From this, we can see that implementing an Agent in a specific scenario requires customizing two key components:</p><ol><li>The few-shot examples in the prompt template</li><li>The definition of external tools for function calling</li></ol><p>The few-shot examples essentially mirror structured human thinking patterns. Reviewing prompt templates for different design patterns is a great way to understand Agent design. Once you grasp this approach, it can be applied to other design patterns similarly.</p><h3>Plan and Solve Pattern</h3><p>As the name suggests, this design pattern involves planning first and then executing. If ReAct is more suitable for tasks like “getting pen from the the desk” then Plan &amp; Solve is better suited for tasks like “making a cup of flat white.” You need to plan, and the plan might change during the process (for example, if you open the fridge and find no milk, you would add “buy milk” as a new step in the plan).</p><p>Regarding the prompt template, the paper’s title makes it clear: <a href="https://arxiv.org/abs/2305.04091"><em>Plan-and-Solve Prompting: Improving Zero-Shot Chain-of-Thought Reasoning by Large Language Models</em>.</a> In short, it’s about improving zero-shot capabilities. The following image shows some PS-Plan and Solve prompts provided in the author’s code.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*5PDTqST0eh-qYCbX-uhzcQ.png" /><figcaption><a href="https://github.com/AGI-Edgerunners/Plan-and-Solve-Prompting">Link</a></figcaption></figure><p>Its architecture is composed as follows:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*WQarFPrWeatrSZHeRoHjTQ.png" /></figure><ul><li>Planner: Responsible for enabling the LLM to generate a multi-step plan to complete a large task. In the code, there are both a Planner and a Replanner. The Planner is responsible for generating the plan initially; the Replanner, on the other hand, comes into play after each individual task is completed, adjusting the plan based on the current progress. Therefore, the Replanner’s prompt includes not only the zero-shot input but also the goal, the original plan, and the status of completed steps.</li><li>Executor: Receives the user’s query and the steps from the plan, then calls one or more tools to accomplish the task.</li></ul><h3>Other Workflow Design Patterns</h3><p>Here we give the brief introduction of other design patterns listed above.</p><h4><a href="https://github.com/langchain-ai/langgraph/blob/main/docs/docs/tutorials/rewoo/rewoo.ipynb">Reason without Observation (REWOO)</a></h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*srID4HdpUFpE-5wlSQHg5g.png" /></figure><p>REWOO (Reason without Observation) is a variation on the observation process seen in ReAct. While ReAct follows the structure: Thought → Action → Observation, REWOO simplifies this by removing the explicit observation step. Instead, it implicitly embeds observation into the next execution unit. In practice, the next executor automatically observes the outcome of the previous step, streamlining the process.</p><h4><a href="https://github.com/langchain-ai/langgraph/blob/main/docs/docs/tutorials/llm-compiler/LLMCompiler.ipynb">LLMCompiler</a></h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*_8FP1sYMWwcVZaD8tObjVA.png" /></figure><p>In computer science, a compiler refers to the orchestration of tasks to optimize computational efficiency. The concept behind An LLM Compiler for Parallel Function Calling, as outlined in the original paper, is simple yet effective: it aims to enhance efficiency by enabling parallel function calls. For instance, if a user asks, “What’s the difference between AWS Glue and MWAA?”, the compiler would search for the AWS services definition simultaneously and combine the results, rather than handling each query sequentially.</p><h4><a href="https://github.com/langchain-ai/langgraph/blob/main/docs/docs/tutorials/reflection/reflection.ipynb">Basic Reflection</a></h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*9543fUoJVC5xq9Ot5aLbvw.png" /></figure><p>Basic Reflection can be compared to a feedback loop between a student (the Generator) and a teacher (the Reflector). The student completes an assignment, the teacher provides feedback, and the student revises their work based on this feedback, repeating the cycle until the task is satisfactorily completed.</p><h4><a href="https://github.com/langchain-ai/langgraph/blob/main/docs/docs/tutorials/reflexion/reflexion.ipynb">Reflexion</a></h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*owP9OsMtoMBNT5BnGYR1GA.png" /></figure><p>Reflexion builds on Basic Reflection, incorporating principles of reinforcement learning. Described in the paper Reflexion: Language Agents with Verbal Reinforcement Learning, this approach goes beyond simple feedback. It evaluates the response using external data and forces the model to address any redundancies or omissions, making the reflective process more robust and the output more refined.</p><h4><a href="https://github.com/langchain-ai/langgraph/blob/main/docs/docs/tutorials/lats/lats.ipynb">Language Agent Tree Search (LATS)</a></h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*IpwqQIk8i7k-lLf9HRQHSw.png" /></figure><p>LATS is detailed in the paper <a href="https://arxiv.org/abs/2310.04406"><em>Language Agent Tree Search Unifies Reasoning, Acting, and Planning in Language Models.</em></a> It combines several techniques, including Tree Search, ReAct, and Plan &amp; Solve. LATS uses tree search to assess outcomes (drawing from reinforcement learning), while also integrating reflection to achieve optimal results. In essence, LATS can be represented by the following formula:</p><p><em>LATS = Tree Search + ReAct + Plan &amp; Solve + Reflection + Reinforcement Learning.</em></p><p>In terms of prompt design, the difference between LATS and earlier methods such as Reflection, Plan &amp; Solve, and ReAct is minimal. The key addition is the tree search evaluation step and the return of those evaluated results within the task context. Architecturally, LATS involves multiple rounds of Basic Reflection, with several Generators and Reflectors working collaboratively.</p><h4><a href="https://arxiv.org/abs/2402.03620">Self-Discovery</a></h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*dIw0pcymwtjUMVmYnwrBqw.png" /></figure><p>The core of Self-Discovery is to allow the large model to reflect at a more granular level. While Plan &amp; Solve focuses on whether a task requires additional steps or adjustments, Self-Discovery goes further, encouraging reflection on the task itself. This involves evaluating each component of the task and the execution of those components.</p><h4><a href="https://github.com/langchain-ai/langgraph/blob/main/docs/docs/tutorials/storm/storm.ipynb">Storm</a></h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ixKPpWglH89IdRdoHsFQQA.png" /></figure><p>Storm, outlined in the paper <a href="https://arxiv.org/abs/2402.14207"><em>Assisting in Writing Wikipedia-like Articles From Scratch with Large Language Models</em></a>, introduces a workflow for generating comprehensive articles from scratch, similar to a Wikipedia entry. The agent first uses external tools to search for information and generate an outline. Then, it produces content for each section based on the outline, making the process structured and efficient.</p><h3>Conclusion</h3><p>In this post, we explore various AI agent workflow design patterns, we categorize the patterns into Reflection-focused and Planning-focused, showcasing examples like the ReAct and Plan-Solve patterns. For developing purposes, these workflow patterns emphasize that workflows act as orchestrators for tasks within an agent. Each node in the workflow represents actions such as LLM tasks, function calls, or Retrieval-Augmented Generation (RAG). This structure enables the agent to plan, execute tasks, and iterate, simulating human reasoning.</p><p>We are currently developing our own agent based on these principles, utilizing workflows as flexible task orchestrators to handle different problems. More details on our agent implementation will be covered in the next post.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=cf9e1f609696" width="1" height="1" alt=""><hr><p><a href="https://medium.com/binome/ai-agent-workflow-design-patterns-an-overview-cf9e1f609696">AI Agent Workflow Design Patterns — An Overview</a> was originally published in <a href="https://medium.com/binome">Binome</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[AI Agent Function Calling: What Really Matters]]></title>
            <link>https://medium.com/binome/ai-agent-function-calling-what-really-matters-bfda0cb7cbe7?source=rss----35517879563d---4</link>
            <guid isPermaLink="false">https://medium.com/p/bfda0cb7cbe7</guid>
            <category><![CDATA[llm-agent]]></category>
            <category><![CDATA[genai]]></category>
            <category><![CDATA[llm-function-calling]]></category>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[llm-evaluation]]></category>
            <dc:creator><![CDATA[Craig Li, Ph.D]]></dc:creator>
            <pubDate>Thu, 29 Aug 2024 14:13:36 GMT</pubDate>
            <atom:updated>2024-08-29T14:13:36.905Z</atom:updated>
            <content:encoded><![CDATA[<p><strong>TL;DR</strong></p><p><em>An Agent’s function calling execution capability </em><strong><em>heavily</em></strong><em> depends on the underlying model.</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*W5DHxI0Wuuv18iCA" /></figure><h3>LLM Function Calling Overview</h3><p>When it comes to function calling in large language models (LLMs), the ability of a model to accurately respond to a function call request based on input prompts is absolutely crucial. In our experiments, we’ve seen how testing the same prompt across different models can lead to significantly varied results, which really highlight the importance of choosing the right model.</p><p>UC Berkeley recently released a <a href="https://gorilla.cs.berkeley.edu/leaderboard.html">function calling leaderboard</a>, where the top-performing GPT-4 model achieves an accuracy rate of <strong>85.65%</strong> (as of 28 Aug 2024).</p><p><a href="https://gorilla.cs.berkeley.edu/leaderboard.html">Berkeley Function Calling Leaderboard (BFCL) V4</a></p><p>That’s impressive, but let’s be honest — it’s still not where we need it to be for business requirements.</p><p>Unfortunately, developers have limited control over an LLM’s function calling capabilities. Even with precise definitions of API functions, parameter names, and descriptions — often well-documented by model providers — there are still challenges.</p><ul><li>OpenAI’s function calling guide: <a href="https://platform.openai.com/docs/guides/function-calling">OpenAI Platform</a></li><li>Google’s function calling guide: <a href="https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/function-calling#function-description-bp">Google Cloud Function Calling</a></li><li>Anthropic’s function calling guide: <a href="https://docs.anthropic.com/en/docs/build-with-claude/tool-use">Tool use (function calling) — Anthropic</a></li></ul><p>Given that <strong>execution accuracy</strong> remains less than perfect — topping at around 85.65% as per above — , this kind of inaccuracy is simply not acceptable for many business needs. That’s why it’s so important to have fallback strategies in place when function calling doesn’t go as planned. For example:</p><ul><li>Redirecting to human support in customer service scenarios.</li><li>Translating API error messages into language that’s easy for users to understand and act on</li></ul><p>In <a href="https://medium.com/binome/designing-llm-based-agents-key-principles-part-1-7e8c6fe3ddaf">Chapter 1 </a>of our proposed <strong>Multi-agent Framework</strong>, we talked about how an agent should be designed with a specific-enough task in mind to perform well. In this article, we — my colleague <a href="https://www.linkedin.com/in/eisha-sultana/">Eisha</a>, and I — stick to that principle, and share our experiment with function calling, which we built as part of a Proof of Concept (PoC) for a recent collaboration project. We’ll also walk you through what we like to call the <em>Agent Development Life Cycle</em>, covering everything from gathering requirements to creating a ChatBot powered by an AI agent. We hope you’ll enjoy this journey with us.</p><h3>Requirement &amp; Design</h3><p>The overall platform requirement was fairly substantial, we therefore attempted to break it into smaller domains and deliverables. One specific task was to build an API that allows users to find schools given a location, starting with <strong>UK postcodes</strong>. To make the agent’s task specific enough, we decided to use just one function. Here’s the design:</p><ol><li>A dataset containing schools and their geolocation.</li><li>A function that takes the geolocation and finds schools in the dataset.</li><li>An AI Agent that handles user input for school search and calls the function to generate responses.</li></ol><h3>Implementation</h3><p>We sourced datasets (such as schools data and postcode-to-coordinate data, from UK government public data repositories), then built a data pipeline to process the required data. We used <strong>SQLMesh</strong> for the data pipeline and <strong>DuckDB</strong> as the database. Here’s a glimpse of the pipeline lineage:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*NKNF3dyWPUqQKYdckTK2IQ.png" /></figure><p>The function is straightforward: given a postcode, we convert it to coordinates, and given the coordinates, we find the closest schools and return the top N results.</p><pre>from phi.tools import Toolkit<br> ...<br> class SchoolSearch(Toolkit):<br>    ...<br>    # Function to find nearest schools<br>    def find_nearest_schools(self, postcode: str, num_neighbors: int = 5) -&gt; dict:<br>        &quot;&quot;&quot;Runs a number of neighbor&#39;s schools which close to the postcode<br>        Args:<br>            postcode (str): the valid UK postcode<br>            num_neighbors (int): The number of schools will be returned, default to 5<br>        Returns:<br>            DataFrame: the output schools in a dict<br>        &quot;&quot;&quot;<br>        logger.info(f&#39;Function is called {postcode}, {num_neighbors}&#39;)<br>        file_dir = Path(__file__).parent<br>        db_path = file_dir / &#39;fact.duckdb&#39;<br>        with db.connect(str(db_path.absolute())) as connection:<br>            # Get coordinates for the provided postcode<br>            user_coords = self.get_coordinates_from_postcode(postcode, connection)<br>            logger.info(f&#39;coords {user_coords}&#39;)<br>            if user_coords is None or user_coords.empty:<br>                return f&quot;Cannot find school given postcode {postcode}&quot;# Handle case where postcode is not found<br>            user_lat = user_coords.iloc[0][&#39;latitude&#39;]<br>            user_lon = user_coords.iloc[0][&#39;longitude&#39;]<br>            # Get all school data<br>            df = self.get_all_school_data(connection)<br>            # Calculate distance for each school<br>            df[&#39;distance&#39;] = df.apply(lambda row: self.haversine(user_lat, user_lon, row[&#39;latitude&#39;], row[&#39;longitude&#39;]), axis=1)<br>            # Sort by distance and get the top N nearest schools<br>            nearest_schools = df.sort_values(by=&#39;distance&#39;).head(num_neighbors)<br>            return nearest_schools.to_json(orient=&#39;records&#39;)</pre><p>To create the Agent, here we used <a href="https://www.phidata.com/">PhiData</a></p><pre>from phi.assistant import Assistant<br>from phi.llm.openai import OpenAIChat<br>...<br>assistant = Assistant(<br>                tools=[SchoolSearch()],<br>                llm = OpenAIChat(model=&#39;gpt-4o-mini&#39;, api_key=&#39;&lt;api-key&gt;&#39;)<br>                show_tool_calls=True<br>            )<br>...</pre><p>We then built a FastAPI service around this agent and a simple Chatbot WebUI. Here’s a screenshot:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/656/1*7gLu6KcISVjcNRHJKlp2aA.png" /></figure><p>It does the job, though better prompt engineering would definitely improve responses. Now, let’s move on to the fun part — testing!</p><h3>Testing and Eval</h3><p>We’ll skip unit tests and jump straight into smoke testing. We’ve designed prompts and expected outputs like the following.</p><pre>index,prompt,function_called,function_args_postcode,function_args_number_schools,expected_response<br>1,&quot;Find like 10 schools near SW1A 1AA.&quot;,true,SW1A 1AA,10,&quot;show nearby schools, the school number should be 10&quot;<br>2,&quot;What are the best schools around W1A.&quot;,false,,,ask user to provide the postcode instead<br>3,&quot;Can you list 7 schools close to EC2N 2DB.&quot;,true,EC2N 2DB,7,&quot;show nearby schools, the school number should be 7&quot;<br>...<br>39,&quot;Translate &#39;hello&#39; to French.&quot;,false,,,explain to user that this agent is designed to show schools close to a postcode provided by user<br>40,&quot;What&#39;s the time in New York right now.&quot;,false,,,explain to user that this agent is designed to show schools close to a postcode provided by user</pre><p>The first 20 inputs focused on searching for schools, while the remaining 20 were unrelated dummy prompts. Here is the <a href="https://gist.github.com/GuanyiLi-Craig/1bbea755dca4ee9b61f09b773e8c5b56">full list</a>.</p><p>Given these prompts, we want to evaluate the agent’s performance. The agent should only respond to school search requests and reject irrelevant queries by providing helpful instructions. In smoke testing, we’ll also test different models like Openai GPT-4o, Llama 7B, and other open-source models that can run locally on my machine with Ollama. Here’s the list:</p><pre>gpt-4o<br>gpt-4o-mini<br>llama3<br>llama3.1<br>llama3-groq-tool-use<br>phi3:medium<br>phi3.5<br>gemma2</pre><p>We built an evaluation function to check function calling, and using gpt-4o-mini to check if response (<em>Return usage to user</em> and <em>Correct Response</em>) was as expected. Here’s the flow:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/923/1*A6Bm8-s35fSHW_hHQiJyyw.png" /></figure><p>Then the result is like this</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/651/1*jkbPEOF2Xdo-olcTkr-ERQ.png" /></figure><p>So can we improve the agent’s performance? Since we have limited control over the function call from the model, can we provide better instructions in the prompt? Here’s the updated agent description and instructions:</p><pre>&quot;description&quot;: &quot;You help users find schools near a specific UK postcode. You process queries only when asked about schools around or near a UK postcode and responds with a list based on function call.&quot;,<br>&quot;instructions&quot;: [<br>    &#39;Process input only if it requests information about schools around or near a UK postcode&#39;,<br>    &#39;Handle valid queries that contain a UK postcode or clearly indicate a need for school information based on location.&#39;,<br>    &#39;If the query is unclear or lacks a valid UK postcode, prompt the user to provide more information.&#39;,<br>    &#39;DO NOT use function calling if the query is unclear or lacks a valid UK postcode.&#39;,<br>    &#39;Ensure the agent only processes relevant requests and guides users on its proper usage.&#39;<br>],</pre><p>And here is the results with prompt</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/604/1*w0v0ChHpSFD1azp8Wfxb7A.png" /></figure><p>And a bar chart to compare with and without prompt:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*R9aFcvBkGc1dGTl9Oq3IyA.png" /></figure><p>After analyzing the results ourselves, we discovered that the llama3-groq-tool-use model actually could perform better than llama3, llama3.1, phi3.5, and phi3. However, the LLM used in our evaluation function did not accurately assess the responses from the llama3-groq-tool-use model, indicating a need for further refinement in our evaluation LLM’s prompting strategy, which leads to the next round of <em>Agent Development Life Cycle.</em></p><p>Since we modified some of the dependent code, it might be challenging to reproduce the setup. Therefore, we’ve built a Docker image. We simplified the code, exposed some variables, and created a docker-compose file, allowing you to try the agent we build with OpenAI GPT-4o-mini. You can run the docker compose and open a browser (<a href="http://localhost:8000/">http://localhost:8000/</a>) to try the school search agent.</p><pre>version: &#39;3.8&#39;<br>services:<br>  binome-school-search-agent:<br>    image: guanyili/binome-school-search-agent<br>    environment:<br>      ENV: docker<br>      OPENAI_API_KEY: &lt;your-openai-api-key&gt;<br>    ports:<br>      - &quot;8000:8000&quot;<br>    extra_hosts:<br>      - &quot;host.docker.internal:host-gateway&quot;</pre><h3>Summary</h3><p>In this experiment, we detailed our journey from initial requirements to the creation of a fully functional AI Agent ready for demonstration. Throughout this process, we highlighted the critical importance of selecting the right model for function calling in AI agents. We also explored fallback strategies and refined prompt techniques to enhance the agent’s performance. While function calling accuracy is still an area requiring further development, this experiment establishes a strong foundation for building and improving AI agents capable of handling business tasks.</p><p>Additionally, we identified a key issue that needs to be addressed: the need to trace data flow within the agent. We prefer to not rely on the LangChain ecosystem (especially considering that LangSmith is not free), therefore we plan to tackle agent <strong>Observability</strong> in the upcoming post(s) using widely adopted software design patterns. Another important thing to address would be, how would things change in a multi-agent setup, i.e. where there is a need for orchestration and aggregation of values returned by agents (into one unified and cohesive response sent back to the users).</p><p><strong>Stay tuned for more insights as we continue to publicly refine and advance our approach!</strong></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=bfda0cb7cbe7" width="1" height="1" alt=""><hr><p><a href="https://medium.com/binome/ai-agent-function-calling-what-really-matters-bfda0cb7cbe7">AI Agent Function Calling: What Really Matters</a> was originally published in <a href="https://medium.com/binome">Binome</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>