Skip to content

Commit eb69933

Browse files
Merge branch 'main' into v2.0
2 parents 60d41b5 + 212bf9d commit eb69933

File tree

33 files changed

+1083
-843
lines changed

33 files changed

+1083
-843
lines changed

.github/workflows/code-quality.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ on:
77
branches: [ main, v2.0 ]
88
workflow_dispatch:
99

10+
permissions:
11+
contents: read
12+
1013
jobs:
1114
quality:
1215
runs-on: ubuntu-latest

.github/workflows/docs.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,13 @@ jobs:
3939
- name: Build documentation
4040
run: |
4141
cd docs
42+
poetry run make singlehtml
4243
poetry run make html
4344
45+
- name: Generate llms.txt
46+
run: |
47+
poetry run python scripts/generate_llms_txt.py
48+
4449
- name: Upload artifact
4550
uses: actions/upload-pages-artifact@v3
4651
with:

README.md

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,55 @@
22

33
<img src="./.assets/logo.png" alt="Atomic Agents" width="350"/>
44

5-
---
6-
75
[![PyPI version](https://badge.fury.io/py/atomic-agents.svg)](https://badge.fury.io/py/atomic-agents)
6+
[![Documentation](https://img.shields.io/badge/docs-read%20the%20docs-blue?logo=readthedocs&style=flat-square)](https://brainblend-ai.github.io/atomic-agents/)
7+
[![Build Docs](https://github.com/BrainBlend-AI/atomic-agents/actions/workflows/docs.yml/badge.svg)](https://github.com/BrainBlend-AI/atomic-agents/actions/workflows/docs.yml)
8+
[![Code Quality](https://github.com/BrainBlend-AI/atomic-agents/actions/workflows/code-quality.yml/badge.svg)](https://github.com/BrainBlend-AI/atomic-agents/actions/workflows/code-quality.yml)
9+
[![Discord](https://img.shields.io/badge/chat-on%20discord-7289DA?logo=discord&style=flat-square)](https://discord.gg/J3W9b5AZJR)
10+
[![PyPI downloads](https://img.shields.io/pypi/dm/atomic-agents?style=flat-square)](https://pypi.org/project/atomic-agents/)
11+
[![Python Versions](https://img.shields.io/pypi/pyversions/atomic-agents?style=flat-square)](https://pypi.org/project/atomic-agents/)
12+
[![License: MIT](https://img.shields.io/badge/license-MIT-yellow?style=flat-square)](LICENSE)
13+
[![GitHub Stars](https://img.shields.io/github/stars/BrainBlend-AI/atomic-agents?style=social)](https://github.com/BrainBlend-AI/atomic-agents/stargazers)
14+
[![GitHub Forks](https://img.shields.io/github/forks/BrainBlend-AI/atomic-agents?style=social)](https://github.com/BrainBlend-AI/atomic-agents/network/members)
15+
16+
## Table of Contents
17+
18+
- [Atomic Agents](#atomic-agents)
19+
- [Table of Contents](#table-of-contents)
20+
- [Overview](#overview)
21+
- [Documentation](#documentation)
22+
- [Watch the Overview Video](#watch-the-overview-video)
23+
- [Watch the Quickstart Video](#watch-the-quickstart-video)
24+
- [Why Atomic Agents?](#why-atomic-agents)
25+
- [Anatomy of an Agent](#anatomy-of-an-agent)
26+
- [Installation](#installation)
27+
- [Project Structure](#project-structure)
28+
- [Quickstart \& Examples](#quickstart--examples)
29+
- [Context Providers](#context-providers)
30+
- [Using Context Providers](#using-context-providers)
31+
- [Chaining Schemas and Agents](#chaining-schemas-and-agents)
32+
- [Example: Generating Queries for Different Search Providers](#example-generating-queries-for-different-search-providers)
33+
- [Running the CLI](#running-the-cli)
34+
- [Provider \& Model Compatibility](#provider--model-compatibility)
35+
- [Atomic Forge](#atomic-forge)
36+
- [Contributing](#contributing)
37+
- [License](#license)
38+
- [Star History](#star-history)
39+
40+
## Overview
841

942
The Atomic Agents framework is designed around the concept of atomicity to be an extremely lightweight and modular framework for building Agentic AI pipelines and applications without sacrificing developer experience and maintainability. The framework provides a set of tools and agents that can be combined to create powerful applications. It is built on top of [Instructor](https://github.com/jxnl/instructor) and leverages the power of [Pydantic](https://docs.pydantic.dev/latest/) for data and schema validation and serialization.
1043
All logic and control flows are written in Python, enabling developers to apply familiar best practices and workflows from traditional software development without compromising flexibility or clarity.
1144

12-
**NEW: We now also have an official subreddit at [/r/AtomicAgents](https://www.reddit.com/r/AtomicAgents/) - Be sure to join!**
45+
**NEW: Join our community on Discord at [discord.gg/J3W9b5AZJR](https://discord.gg/J3W9b5AZJR) and our official subreddit at [/r/AtomicAgents](https://www.reddit.com/r/AtomicAgents/)!**
1346

14-
---
47+
## Documentation
48+
49+
[![Read the Docs](https://img.shields.io/badge/docs-read%20the%20docs-blue?logo=readthedocs&style=for-the-badge)](https://brainblend-ai.github.io/atomic-agents/)
50+
51+
> 🚀 Ready to explore our documentation? Dive in below!
52+
53+
[Visit the Documentation Site »](https://brainblend-ai.github.io/atomic-agents/)
1554

1655
If you want to learn more about the motivation and philosophy behind Atomic Agents, [I suggest reading this Medium article (no account needed)](https://ai.gopubby.com/want-to-build-ai-agents-c83ab4535411?sk=b9429f7c57dbd3bda59f41154b65af35) or check out the overview video below:
1756

@@ -327,10 +366,6 @@ The `atomic-assembler` CLI gives you complete control over your tools, avoiding
327366

328367
Atomic Agents depends on the [Instructor](https://github.com/jxnl/instructor) package. This means that in all examples where OpenAI is used, any other API supported by Instructor can also be used—such as Ollama, Groq, Mistral, Cohere, Anthropic, Gemini, and more. For a complete list, please refer to the Instructor documentation on its [GitHub page](https://github.com/jxnl/instructor).
329368

330-
## API Documentation
331-
332-
API documentation can be found [here](https://brainblend-ai.github.io/atomic-agents/).
333-
334369
## Atomic Forge
335370

336371
Atomic Forge is a collection of tools that can be used with Atomic Agents to extend its functionality. Current tools include:
@@ -343,7 +378,7 @@ For more information on using and creating tools, see the [Atomic Forge README](
343378

344379
## Contributing
345380

346-
We welcome contributions! Please see the [Developer Guide](/guides/DEV_GUIDE.md) for detailed information on how to contribute to Atomic Agents. Here are some quick steps:
381+
We welcome contributions! Please see the [Contributing Guide](/docs/contributing.md) for detailed information on how to contribute to Atomic Agents. Here are some quick steps:
347382

348383
1. Fork the repository
349384
2. Create a new branch (`git checkout -b feature-branch`)

atomic-agents/atomic_agents/agents/base_agent.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,18 +59,17 @@ class BaseAgentOutputSchema(BaseIOSchema):
5959

6060
class BaseAgentConfig(BaseModel):
6161
client: instructor.client.Instructor = Field(..., description="Client for interacting with the language model.")
62-
model: str = Field("gpt-4o-mini", description="The model to use for generating responses.")
63-
memory: Optional[AgentMemory] = Field(None, description="Memory component for storing chat history.")
62+
model: str = Field(default="gpt-4o-mini", description="The model to use for generating responses.")
63+
memory: Optional[AgentMemory] = Field(default=None, description="Memory component for storing chat history.")
6464
system_prompt_generator: Optional[SystemPromptGenerator] = Field(
65-
None, description="Component for generating system prompts."
65+
default=None, description="Component for generating system prompts."
6666
)
6767
system_role: Optional[str] = Field(
68-
"system", description="The role of the system in the conversation. None means no system prompt."
68+
default="system", description="The role of the system in the conversation. None means no system prompt."
6969
)
7070
model_config = {"arbitrary_types_allowed": True}
7171
model_api_parameters: Optional[dict] = Field(None, description="Additional parameters passed to the API provider.")
7272

73-
7473
class BaseAgent[InputSchema: BaseIOSchema, OutputSchema: BaseIOSchema]:
7574
"""
7675
Base class for chat agents.

atomic-agents/atomic_agents/lib/factories/schema_transformer.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ def create_model_from_schema(
109109
model_name,
110110
__base__=BaseIOSchema,
111111
__doc__=docstring or f"Dynamically generated Pydantic model for {model_name}",
112+
__config__={"title": tool_name_literal},
112113
**fields,
113114
)
114115

atomic-examples/deep-research/deep_research/tools/webpage_scraper.py

Lines changed: 46 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ class WebpageScraperToolOutputSchema(BaseIOSchema):
4646

4747
content: str = Field(..., description="The scraped content in markdown format.")
4848
metadata: WebpageMetadata = Field(..., description="Metadata about the scraped webpage.")
49+
error: Optional[str] = Field(None, description="Error message if the scraping failed.")
4950

5051

5152
#################
@@ -213,38 +214,47 @@ def run(self, params: WebpageScraperToolInputSchema) -> WebpageScraperToolOutput
213214
Returns:
214215
WebpageScraperToolOutputSchema: The output containing the markdown content and metadata.
215216
"""
216-
# Fetch webpage content
217-
html_content = self._fetch_webpage(str(params.url))
218217

219-
# Parse HTML with BeautifulSoup
220-
soup = BeautifulSoup(html_content, "html.parser")
218+
try:
219+
# Fetch webpage content
220+
html_content = self._fetch_webpage(str(params.url))
221221

222-
# Extract main content using custom extraction
223-
main_content = self._extract_main_content(soup)
222+
# Parse HTML with BeautifulSoup
223+
soup = BeautifulSoup(html_content, "html.parser")
224224

225-
# Convert to markdown
226-
markdown_options = {
227-
"strip": ["script", "style"],
228-
"heading_style": "ATX",
229-
"bullets": "-",
230-
"wrap": True,
231-
}
225+
# Extract main content using custom extraction
226+
main_content = self._extract_main_content(soup)
232227

233-
if not params.include_links:
234-
markdown_options["strip"].append("a")
228+
# Convert to markdown
229+
markdown_options = {
230+
"strip": ["script", "style"],
231+
"heading_style": "ATX",
232+
"bullets": "-",
233+
"wrap": True,
234+
}
235235

236-
markdown_content = markdownify(main_content, **markdown_options)
236+
if not params.include_links:
237+
markdown_options["strip"].append("a")
237238

238-
# Clean up the markdown
239-
markdown_content = self._clean_markdown(markdown_content)
239+
markdown_content = markdownify(main_content, **markdown_options)
240240

241-
# Extract metadata
242-
metadata = self._extract_metadata(soup, Document(html_content), str(params.url))
241+
# Clean up the markdown
242+
markdown_content = self._clean_markdown(markdown_content)
243243

244-
return WebpageScraperToolOutputSchema(
245-
content=markdown_content,
246-
metadata=metadata,
247-
)
244+
# Extract metadata
245+
metadata = self._extract_metadata(soup, Document(html_content), str(params.url))
246+
247+
return WebpageScraperToolOutputSchema(
248+
content=markdown_content,
249+
metadata=metadata,
250+
)
251+
except Exception as e:
252+
# Create empty/minimal metadata with at least the domain
253+
domain = urlparse(str(params.url)).netloc
254+
minimal_metadata = WebpageMetadata(title="Error retrieving page", domain=domain)
255+
256+
# Return with error message in the error field
257+
return WebpageScraperToolOutputSchema(content="", metadata=minimal_metadata, error=str(e))
248258

249259

250260
#################
@@ -266,12 +276,18 @@ def run(self, params: WebpageScraperToolInputSchema) -> WebpageScraperToolOutput
266276
)
267277
)
268278

269-
console.print(Panel.fit("Metadata", style="bold green"))
270-
console.print(result.metadata.model_dump_json(indent=2))
279+
# Check if there was an error during scraping, otherwise print the results
280+
if result.error:
281+
console.print(Panel.fit("Error", style="bold red"))
282+
console.print(f"[red]{result.error}[/red]")
283+
else:
284+
console.print(Panel.fit("Metadata", style="bold green"))
285+
console.print(result.metadata.model_dump_json(indent=2))
286+
287+
console.print(Panel.fit("Content Preview (first 500 chars)", style="bold green"))
288+
# To show as markdown with proper formatting
289+
console.print(Panel.fit("Content as Markdown", style="bold green"))
290+
console.print(Markdown(result.content[:500]))
271291

272-
console.print(Panel.fit("Content Preview (first 500 chars)", style="bold green"))
273-
# To show as markdown with proper formatting
274-
console.print(Panel.fit("Content as Markdown", style="bold green"))
275-
console.print(Markdown(result.content[:500]))
276292
except Exception as e:
277293
console.print(f"[red]Error:[/red] {str(e)}")

atomic-examples/mcp-agent/example-client/poetry.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

atomic-examples/mcp-agent/example-client/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ authors = ["Your Name <[email protected]>"]
88
python = ">=3.12,<4.0"
99
atomic-agents = { path = "../../../", develop = true }
1010
example-mcp-server = { path = "../example-mcp-server", develop = true }
11-
pydantic = ">=2.0.0"
11+
pydantic = ">=2.10.3,<3.0.0"
1212
rich = ">=13.0.0"
1313
openai = ">=1.0.0"
1414
mcp = {extras = ["cli"], version = "^1.6.0"}

atomic-examples/orchestration-agent/poetry.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

atomic-examples/orchestration-agent/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ readme = "README.md"
99
python = ">=3.12,<4.0"
1010
atomic-agents = {path = "../..", develop = true}
1111
instructor = "^1.6.1"
12-
pydantic = "^2.9.2"
12+
pydantic = ">=2.10.3,<3.0.0"
1313
sympy = "^1.13.3"
1414
python-dotenv = ">=1.0.1,<2.0.0"
1515
openai = ">=1.35.12,<2.0.0"

atomic-examples/quickstart/quickstart/4_basic_chatbot_different_providers.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,14 +57,20 @@ def setup_client(provider):
5757
mode=instructor.Mode.JSON,
5858
)
5959
model = "gemini-2.0-flash-exp"
60+
elif provider == "6" or provider == "openrouter":
61+
from openai import OpenAI as OpenRouterClient
62+
63+
api_key = os.getenv("OPENROUTER_API_KEY")
64+
client = instructor.from_openai(OpenRouterClient(base_url="https://openrouter.ai/api/v1", api_key=api_key))
65+
model = "mistral/ministral-8b"
6066
else:
6167
raise ValueError(f"Unsupported provider: {provider}")
6268

6369
return client, model
6470

6571

6672
# Prompt the user to choose a provider from one in the list below.
67-
providers_list = ["openai", "anthropic", "groq", "ollama", "gemini"]
73+
providers_list = ["openai", "anthropic", "groq", "ollama", "gemini", "openrouter"]
6874
y = "bold yellow"
6975
b = "bold blue"
7076
g = "bold green"

atomic-examples/web-search-agent/README.md

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,10 @@ Make sure to add these lines to `settings.tml`:
5959
SEARXNG_BASE_URL=your_searxng_instance_url
6060
```
6161

62-
Replace `your_openai_api_key` with your actual OpenAI API key and `your_searxng_instance_url` with the URL of your SearXNG instance.
62+
Replace `your_openai_api_key` with your actual OpenAI API key and `your_searxng_instance_url` with the URL of your SearXNG instance.
63+
If you do not have a SearxNG instance, see the instructions below to set up one locally with docker.
6364

64-
1. Run the Web Search Agent:
65+
2. Run the Web Search Agent:
6566

6667
```bash
6768
poetry run python web_search_agent/main.py
@@ -75,6 +76,30 @@ Make sure to add these lines to `settings.tml`:
7576
4. The Question Answering Agent analyzes the search results and formulates a detailed answer.
7677
5. The main script presents the answer, along with references and follow-up questions.
7778

79+
## SearxNG Setup with docker
80+
81+
From the [official instructions](https://docs.searxng.org/admin/installation-docker.html):
82+
83+
```shell
84+
mkdir my-instance
85+
cd my-instance
86+
export PORT=8080
87+
docker pull searxng/searxng
88+
docker run --rm \
89+
-d -p ${PORT}:8080 \
90+
-v "${PWD}/searxng:/etc/searxng" \
91+
-e "BASE_URL=http://localhost:$PORT/" \
92+
-e "INSTANCE_NAME=my-instance" \
93+
searxng/searxng
94+
```
95+
96+
Set the `SEARXNG_BASE_URL` environment variable to `http://localhost:8080/` in your `.env` file.
97+
98+
99+
Note: for the agent to communicate with SearxNG, the instance must enable the JSON engine, which is disabled by default.
100+
Edit `/etc/searxng/settings.yml` and add `- json` in the `search.formats` section, then restart the container.
101+
102+
78103
## Customization
79104

80105
You can customize the Web Search Agent by modifying the following:

atomic-examples/web-search-agent/web_search_agent/agents/query_agent.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44
from typing import List
55
from atomic_agents.agents.base_agent import BaseIOSchema, BaseAgent, BaseAgentConfig
66
from atomic_agents.lib.components.system_prompt_generator import SystemPromptGenerator
7-
8-
97
class QueryAgentInputSchema(BaseIOSchema):
108
"""This is the input schema for the QueryAgent."""
119

0 commit comments

Comments
 (0)