Photo by Glenn Carstens-Peters on Unsplash
Intro
If you're on the job search like myself (at time of writing), I'm sure you'll know how critical it is to spend your time efficiently.
You certainly don't want to spend additional time on admin, manually copy-pasting the details from the job description into whatever tool you use to track job applications.
That's why I've made a tool to automate this part of the process, a multi-agent system which auto extracts and pastes the relevant parts of job posts you care about directly into your Excel workbook sheet of choice!
In this article, I cover my learnings figuring out how to work with Google ADK and Composio while building out this system - covering the architecture, the issues encountered and final takeaways for anyone wanting to tackle their own projects using these technologies!
Hope you enjoy!
Google ADK/Composio Briefer
Before we jump into things, I thought it would be worth giving a brief overview of Google's Agent Development Kit and Composio
(I'm assuming in this overview a prior basic understanding of LLMs, agents, tools/MCP servers - if you're not already familiar with these terms they might be worth a google search first)
Google ADK
Google's Agent Development Kit (ADK) is a scaffolding framework for building production fit agent-based systems.
It offers a lot of flexibility around composing systems, especially v2.0 which introduced graph-based workflows which made it quite straightforward to implement conditional logic and more complex deterministic systems (compared to offerings of pre v2).
The Google ADK supports SDKs across a number of languages, but the best supported SDK (alongside Go) at current is Python, so although I'm usually more of a TypeScript user, this dictated my language choice in this project.
A handful of ADK-specific primitives come up repeatedly later in this article, so I'm defining them here sooner than later!:
output_schema- a Pydantic model you attach to anAgentto constrain its final response to a specific JSON shape (e.g.{header: value}pairs matching a spreadsheet's columns), rather than free-form text.
Callbacks (e.g.
after_model_callback) - functions you hook into an agent's lifecycle to inspect or rewrite what it's about to do or just produced, without changing the agent's own instruction/prompt.
ctx.state- a shared key-value store (a "blackboard") passed through every node in aWorkflow, letting one node write a value (e.g. the extracted job URL) and a later node read it back, including interpolating it directly into another agent's instruction string.
If any of these feel abstract right now, they'll click once you see them in context later in the article.
Composio
Composio is a managed platform which makes tooling integrations accessible to AI agents.
It offers toolkits for working with a large number of platforms, from GitHub to Bitwarden, and as well as being model agnostic it handles the auth to these platforms for you (which is a major reason why I chose to use it!).
Generally speaking, it operates by exposing the signatures for a set of 'meta tools' to your agent which based on the query the agent sends finds and returns the signatures for the tools Composio believes you need to perform the requested task (avoiding context bloat that would otherwise occur in loading in the 200+ tool signatures Composio could present to the agent!).
Now you've got that context in mind, lets discuss the project!
Why an Agent-Based Approach?
Photo by Immo Wegmann on Unsplash
Before I got started, I knew what I ultimately wanted should be quite straightforward.
I needed the system to reliably navigate to a job URL, extract the details I was interested in and put them into a new row in my excel workbook.
In an ideal world you wouldn't need agentic capabilities for this process at all.
You should be able to just make a series of API calls, making a GET request to a given job post URL and look through structured HTML in the response for the fields.
This however is not the reality for the combined following reasons:
Job Posts/descriptions don't all follow the same structure:,
so you'd need an approach which maps to every platform's different approach
i.e. terms used for salary, what headings they put role responsibilities under etc.
Web pages for job posts can be client-side rendered:,
so you may not be able to get the details you're wanting without spinning up a browser environment to render the page in
Where pages are client side rendered important content may not be immediately visible:
so it will need user interaction to reveal, (e.g. clicking a dropdown to find out the Role responsibilities)
I did a search for tools online and did find a few that as it turned out for me used agents to solve the problem.
The only issue being that for one reason or another there was no good fit for my use case, i.e. namely platforms used an agent to get the content needed from a job post but forced using their bespoke spreadsheet software in doing so.
This did lead to me however deciding that bringing AI agents into the extraction process would be a good way to tackle the unpredictability of job posting structures, given I provided appropriate tools, i.e. for rendering/searching page content in the browser.
I therefore embarked after on mapping out the architecture!
Architecture Overview
A high level abstraction of the system architecture, see ARCHITECTURE.MD in the GitHub Repo for the full diagram
Alongside some of my learnings from the βDevelop Agents with ADKβ skill path I was taking with Google and some research into the ADK docs I drew up a less detailed version of the architecture you see above.
Less detailed as admittedly when I drew up the mermaid diagram my understanding of what was possible and the level of flexibility and efficiencies which could be achieved in composing ADK systems was lower.
I realised this initially, but I decided as this was very much tooling I wanted to meet just my own requirements this was a less relevant concern for me at first (although in retrospect it certainly should have been more relevant!).
I opted to compose the system using a graph based approach in the Google ADK, due to the flexibility offered in declaratively implementing routing and first class support for defining non-agent nodes.
The additional reason being that it would reduce the number of breaking changes Iβd have to work through if I wanted to take in any further in future, very much seeming like the sensible option.
1). Handling Composio tradeoffs
Using Composio was a decision that I made under the assumption that it would be safer and less effort than having to implement auth management myself to OneDrive and Excel.
I believe this has held true, however it introduced a large element of non-determinism to the system that had to be handled (not to mention token cost, although it's on the lighter side with using non-frontier models here).
The two major risks posed were as follows:
- Wide scope for unrestricted tooling access if the agent decided for some reason an irrelevant tool would solve the problem
- Unpredictable response formats in case of runtime errors and even in successful operations!
The first issue was the far easier issue to fix, you could reduce the scope of actions via tools an agent could take through giving a specific allowlist in the Composio SDK to restrict the toolsets and list only the required tools.
Layering on top of this you can provide a clear and specific instruction explicitly mandating specific tools for specific agent nodes.
The second issue of response formats was more difficult to tackle.
Even with a clear and specific instruction and output_schema for the agent to adhere to you can't guarantee what the output format will be, whether the process is a success or an error occurs.
Frankly, there are far too many ways an agent could output that an errors occurred in terms of prose even with a fixed set of failure modes, the same applies to success messages also!
My solution to this was to leverage the ADK's after_model_callback after the agent node had finished all tool calls, intercepting any errors and capture them in the session state under a key of job_spec_details_error (and in which case I set the state job_spec_details to {}).
Only then is the output JSON validated against the Pydantic schema in output_schema.
A subsequent node would then check for errors under job_spec_details_error and surfaces any as user messages before rerunning the agent and (ideally) not running into the issue again.
I set a max retry count of 3 attempts before the system opted to raise an error and exit.
While there are more flexible approaches and more configuration that can be done to improve the system, this approach was sufficient for my purposes.
For example, it would be handy getting an agent to do some light troubleshooting to determine the issue involved and informing the user what's needed to resolve, e.g. API rate limit being met so needing to wait for a period of time before retrying.
2). Flaky gemini models
During development, I've ran a number of times into unavailability of Gemini's flash models.
I can't say I'm too surprised in retrospect as I created a Gemini API Key on the free tier within a test GCP project in Google AI Studio.
However, it's been pretty frustrating when I've been wanting to do some quick tests and been locked out due to model unavailability due to spikes in external usage.
Unless you upgrade the billing tier, I'd recommend as a quicker workaround setting up an environment variable(s) and using these within your agent definitions.
It doesn't solve the root problem but it can be a quicker approach than switching out all agents model fields by hand!
3). Mitigating anti-bot measures
A fair reason why I believe I struggled to find a tool to fix this problem originally is because using agents, web scraping tools or otherwise requires doing your best to mitigate against anti-bot detections and frankly sometimes this isn't always possible.
LinkedIn and Indeed are especially tricky to work around in my experience so far, which can be frustrating when those can be the places you want to target (although frankly I wouldn't advise putting too much focus on Linkedin for searching and making actual job applications - but that's a whole other conversation!)
I created a few custom tools for an agent to use with Playwright which controls a headless chromium instance to navigate and read content from.
This worked all well and fine on more lenient sites for extracting job details, but when it came to Linkedin and Indeed it was a much different story.
With Linkedin, I've decided to give it a pass for now as unless I can use an authentic user-agent (via browser extension control, e.g. chrome) it's pretty tricky.
In addition, you need to be logged in to Linkedin to view jobs which can you put in a tricky situation if you get caught out - perhaps more so than being logged out and getting IP-banned.
However, for other sites I've found using the playwright-stealth python library to be a good mitigation in terms of hiding the tell tale signs of a headless browser fingerprint.
I'll admit this is an area I'm still learning about so I've yet to cover all practical bases here but it's helped me in part here.
Will do my best to update this article around this topic with new approaches as I get stuff sorted here!
Final takeaways/conclusion
Photo by Alexander Grey on Unsplash
If you're tackling an agent-based project with Google ADK and Composio, I'd recommend the following:
- π Allowlist needed tools/toolkits only when using Composio
- π‘οΈ Use ADK agent callbacks to safely handle output format errors
- β‘ Enable quick model switch-outs with environment variables
Hope you enjoyed reading and the above guidance can be of help for you if you embark on your own project with Google ADK and Composio.
If you do, feel free to comment below as would love to check them out.
Additionally, if you have any further questions around this area send me a message on LinkedIn or comment below and I'll answer to the best of my knowledge!
As an aside, if you'd like to checkout the project check out the repo link below.
I've also attached docs links for some of the topics I've covered here for further reading.
Hope you have a great day!
π Links:




Top comments (0)