Skip to content

Commit 0f40ca1

Browse files
refactored the agent to have different backends available
Signed-off-by: Peter Staar <taa@zurich.ibm.com>
1 parent 9f118cc commit 0f40ca1

30 files changed

Lines changed: 1020 additions & 522 deletions

README.md

Lines changed: 66 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,23 @@ Docling-agent simplifies agentic operation on documents, such as writing, editin
1818
- [Targeted editing](examples/example_02_edit_report.py): Load an existing Docling JSON and apply focused edits with natural-language tasks.
1919
- [Schema-guided extraction](examples/example_03_extract_schema.py): Extract typed fields from PDFs/images using a simple schema and produce HTML reports. See examples on curriculum_vitae, papers, invoices, etc.
2020
- [Document enrichment](examples/example_04_enrich_document.py): Enrich existing documents with summaries, search keywords, key entities, and item classifications (language/function).
21-
- Model-agnostic: Plug in different backends via [Mellea](https://github.com/generative-computing/mellea) `model_ids` (e.g., OpenAI GPT OSS, IBM Granite).
21+
- Model-agnostic: Choose `mellea`, `ollama`, `lmstudio`, or `litellm` through backend configuration.
2222
- Simple API surface: Use `agent.run(...)` with `DoclingDocument` in/out; save via `save_as_*` helpers.
2323
- Optional tools: Integrate external tools (e.g., MCP) when available.
2424

2525
Quick start (writing):
2626

2727
```python
28-
from mellea.backends import model_ids
29-
from docling_agent.agents import DoclingWritingAgent
30-
31-
agent = DoclingWritingAgent(model_id=model_ids.OPENAI_GPT_OSS_20B)
28+
from docling_agent.agents import BackendConfig, DoclingWritingAgent, ModelConfig, create_backend
29+
30+
backend = create_backend(
31+
BackendConfig(
32+
type="ollama",
33+
base_url="http://localhost:11434",
34+
models=ModelConfig(reasoning="qwen3:8b", writing="qwen3:8b"),
35+
)
36+
)
37+
agent = DoclingWritingAgent(backend=backend, tools=[])
3238
doc = agent.run("Write a one-page summary about polymers in food packaging.")
3339
doc.save_as_html("report.html")
3440
```
@@ -44,10 +50,16 @@ Below are three minimal, end-to-end examples mirroring the scripts in the exampl
4450
### Write a new document (see [example](examples/example_01_write_report.py)):
4551

4652
```python
47-
from mellea.backends import model_ids
48-
from docling_agent.agents import DoclingWritingAgent
49-
50-
agent = DoclingWritingAgent(model_id=model_ids.OPENAI_GPT_OSS_20B)
53+
from docling_agent.agents import BackendConfig, DoclingWritingAgent, ModelConfig, create_backend
54+
55+
backend = create_backend(
56+
BackendConfig(
57+
type="ollama",
58+
base_url="http://localhost:11434",
59+
models=ModelConfig(reasoning="qwen3:8b", writing="qwen3:8b"),
60+
)
61+
)
62+
agent = DoclingWritingAgent(backend=backend, tools=[])
5163
doc = agent.run("Write a brief report on polymers in food packaging with a small comparison table.")
5264
doc.save_as_html("./scratch/report.html")
5365
```
@@ -58,14 +70,19 @@ Use natural-language tasks to update a Docling JSON. You can run multiple tasks
5870

5971
```python
6072
from pathlib import Path
61-
from mellea.backends import model_ids
6273
from docling_core.types.doc.document import DoclingDocument
63-
from docling_agent.agents import DoclingEditingAgent
74+
from docling_agent.agents import BackendConfig, DoclingEditingAgent, ModelConfig, create_backend
6475

6576
ipath = Path("./examples/example_02_edit_resources/20250815_125216.json")
6677
doc = DoclingDocument.load_from_json(ipath)
6778

68-
agent = DoclingEditingAgent(model_id=model_ids.OPENAI_GPT_OSS_20B)
79+
backend = create_backend(
80+
BackendConfig(
81+
type="mellea",
82+
models=ModelConfig(reasoning="OPENAI_GPT_OSS_20B", writing="OPENAI_GPT_OSS_20B"),
83+
)
84+
)
85+
agent = DoclingEditingAgent(backend=backend, tools=[])
6986
updated = agent.run(task="Put polymer abbreviations in a separate column in the first table.", document=doc)
7087
updated.save_as_html("./scratch/updated_table.html")
7188
```
@@ -76,13 +93,18 @@ Define a simple schema and provide a list of files (PDFs/images). The agent prod
7693

7794
```python
7895
from pathlib import Path
79-
from mellea.backends import model_ids
80-
from docling_agent.agents import DoclingExtractingAgent
96+
from docling_agent.agents import BackendConfig, DoclingExtractingAgent, ModelConfig, create_backend
8197

8298
schema = {"invoice-number": "string", "total": "float", "currency": "string"}
8399
sources = sorted([p for p in Path("./examples/example_03_extract/invoices").rglob("*.*") if p.suffix.lower() in {".pdf", ".png", ".jpg", ".jpeg"}])
84100

85-
agent = DoclingExtractingAgent(model_id=model_ids.OPENAI_GPT_OSS_20B)
101+
backend = create_backend(
102+
BackendConfig(
103+
type="mellea",
104+
models=ModelConfig(reasoning="OPENAI_GPT_OSS_20B", writing="OPENAI_GPT_OSS_20B"),
105+
)
106+
)
107+
agent = DoclingExtractingAgent(backend=backend, tools=[])
86108
report = agent.run(task=str(schema), sources=sources)
87109
report.save_as_html("./scratch/invoices_extraction_report.html")
88110
```
@@ -93,18 +115,44 @@ Run enrichment passes like summaries, keywords, entities, and classifications on
93115

94116
```python
95117
from pathlib import Path
96-
from mellea.backends import model_ids
97118
from docling_core.types.doc.document import DoclingDocument
98-
from docling_agent.agents import DoclingEnrichingAgent
119+
from docling_agent.agents import BackendConfig, DoclingEnrichingAgent, ModelConfig, create_backend
99120

100121
ipath = Path("./examples/example_02_edit_resources/20250815_125216.json")
101122
doc = DoclingDocument.load_from_json(ipath)
102123

103-
agent = DoclingEnrichingAgent(model_id=model_ids.OPENAI_GPT_OSS_20B)
124+
backend = create_backend(
125+
BackendConfig(
126+
type="mellea",
127+
models=ModelConfig(reasoning="OPENAI_GPT_OSS_20B", writing="OPENAI_GPT_OSS_20B"),
128+
)
129+
)
130+
agent = DoclingEnrichingAgent(backend=backend, tools=[])
104131
enriched = agent.run(task="Summarize each paragraph, table, and section header.", document=doc)
105132
enriched.save_as_html("./scratch/enriched_summaries.html")
106133
```
107134

135+
## Backend Configuration
136+
137+
Task files now select the backend explicitly:
138+
139+
```yaml
140+
backend:
141+
type: ollama # mellea | ollama | lmstudio | litellm
142+
base_url: http://localhost:11434
143+
timeout: 120
144+
models:
145+
reasoning: qwen3:8b
146+
writing: qwen3:8b
147+
```
148+
149+
Typical defaults:
150+
151+
- `mellea`: model names like `OPENAI_GPT_OSS_20B`
152+
- `ollama`: model names like `qwen3:8b`
153+
- `lmstudio`: model names like `granite-3.3-8b-instruct`
154+
- `litellm`: routed model names like `openai/gpt-4.1-mini`
155+
108156
## Documentation
109157

110158
**Coming soon**

docling_agent/__init__.py

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,49 @@
1-
from docling_agent.agents import *
1+
from docling_agent.agents import (
2+
AgentTask,
3+
BackendConfig,
4+
BaseBackend,
5+
DoclingEditingAgent,
6+
DoclingEnrichingAgent,
7+
DoclingExtractingAgent,
8+
DoclingOrchestratorAgent,
9+
DoclingRAGAgent,
10+
DoclingWritingAgent,
11+
EnrichTask,
12+
ExtractTask,
13+
LiteLLMBackend,
14+
LMStudioBackend,
15+
MelleaBackend,
16+
ModelConfig,
17+
OllamaBackend,
18+
OutputConfig,
19+
RAGTask,
20+
WriteTask,
21+
create_backend,
22+
load_task,
23+
logger,
24+
)
25+
26+
__all__ = [
27+
"AgentTask",
28+
"BackendConfig",
29+
"BaseBackend",
30+
"DoclingEditingAgent",
31+
"DoclingEnrichingAgent",
32+
"DoclingExtractingAgent",
33+
"DoclingOrchestratorAgent",
34+
"DoclingRAGAgent",
35+
"DoclingWritingAgent",
36+
"EnrichTask",
37+
"ExtractTask",
38+
"LMStudioBackend",
39+
"LiteLLMBackend",
40+
"MelleaBackend",
41+
"ModelConfig",
42+
"OllamaBackend",
43+
"OutputConfig",
44+
"RAGTask",
45+
"WriteTask",
46+
"create_backend",
47+
"load_task",
48+
"logger",
49+
]

docling_agent/agent/base.py

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@
77

88
# from smolagents import MCPClient, Tool, ToolCollection
99
# from smolagents.models import ChatMessage, MessageRole, Model
10-
from mellea.backends.model_ids import ModelIdentifier
1110
from pydantic import BaseModel, ConfigDict
1211

12+
from docling_agent.backends import BaseBackend, create_backend
13+
from docling_agent.task_model import BackendConfig
14+
1315
if TYPE_CHECKING:
1416
from docling_core.types.doc.document import DoclingDocument
1517

@@ -49,24 +51,35 @@ class BaseDoclingAgent(BaseModel):
4951
model_config = ConfigDict(arbitrary_types_allowed=True)
5052

5153
agent_type: DoclingAgentType
52-
model_id: ModelIdentifier
54+
backend: BaseBackend
5355
tools: list
5456

55-
# model needed for reasoning/instruction following
56-
reasoning_model_id: ModelIdentifier | None = None
57-
58-
# model needed for writing, summarizing, etc
59-
writing_model_id: ModelIdentifier | None = None
60-
6157
max_iteration: int = 16
6258

63-
def get_reasoning_model_id(self) -> ModelIdentifier:
64-
"""Return the reasoning model id, falling back to the primary model."""
65-
return self.reasoning_model_id or self.model_id
66-
67-
def get_writing_model_id(self) -> ModelIdentifier:
68-
"""Return the writing model id, falling back to the primary model."""
69-
return self.writing_model_id or self.model_id
59+
@staticmethod
60+
def default_backend() -> BaseBackend:
61+
"""Build the default backend used by existing agent constructors."""
62+
return create_backend(BackendConfig(type="mellea"))
63+
64+
def get_reasoning_model_id(self) -> str:
65+
"""Return the backend-scoped reasoning model id."""
66+
return self.backend.models.reasoning
67+
68+
def get_writing_model_id(self) -> str:
69+
"""Return the backend-scoped writing model id."""
70+
return self.backend.models.writing
71+
72+
def _create_reasoning_session(self, *, system_prompt: str | None = None):
73+
return self.backend.create_session(
74+
model=self.get_reasoning_model_id(),
75+
system_prompt=system_prompt,
76+
)
77+
78+
def _create_writing_session(self, *, system_prompt: str | None = None):
79+
return self.backend.create_session(
80+
model=self.get_writing_model_id(),
81+
system_prompt=system_prompt,
82+
)
7083

7184
@abstractmethod
7285
def run(

0 commit comments

Comments
 (0)