This is an extensible agentic chat assistant for Earth Observability requests which currently supports satellite imagery retrieval and visualization, extracting the intent from user queries, and orchestrating various tools to fulfill those requests. The location is looked up via Nominatim from OpenStreetMap. The desired time range can be specified and defaults to the most recent imagery. A specific band or index (NDVI) can be specified, default is a true color image. By default, cloud-free imagery is preferred (max. 5% cloud coverage, measured within the target region of interest, not on the underlying asset level).
The Copernicus Data Space Ecosystem (CDSE) is used for imagery-related services: Cloud cover is checked based on the cloud probability asset if available (recent imagery) or the scene classification asset (2025 or earlier dates). The WMTS service is used for map rendering, while the STAC API is used to query assets that are then downloaded and analyzed locally depending on the user's query.
An screencast of a simple example query just mentioning a location, implying a request for a recent, cloud-free, true-color image can be found in images/simple_screencast.mp4.
This repository is licensed under the BSD 3-Clause Clear License.
The EOMAS project was funded by the European Space Agency, through the "Sprint4EO" project (managed by OHB Digital Services) that spawned a series of small sprint projects (6 months each) with companies and institutions who were not yet familiar with earth observation, the goal being to bring cutting edge technology and ideas from other domains into EO. EOMAS ended in August 2026, and this assistant is the final resulting demonstrator. No major continuation of this development should be expected at this point.
The EOMAS Assistant is built with:
- LangGraph for orchestration
- Python 3.11+
- Streamlit for the prototype UI
- LLM backend via vLLM / Ollama server
- Pydantic validation for LLM outputs
- Rasterio / GDAL for geospatial data processing
- Titiler for serving merged and warped EO imagery (WIP)
Most of the time, we have been using the EVE-Instruct version of ESA's EVE LLM (earth virtual expert, fine-tuned from Mistral-Small-3.2-24B-Instruct-2506), served via a vLLM server hosted in-house (at MEVIS).
We initially used a quantized version of EVE (Q8) in GGUF format, served via Ollama, but that combination did not support tool calls.
With this code, it is quite easy to use other LLMs (by editing the .env file), but note that the behavior varies between models. We noticed several times that more powerful models uncovered problems with the prompts that were originally designed for EVE-Instruct.
Today, we have to recommend a more powerful model than EVE-Instruct; not only does EVE hallucinate cloud cover too often (just selecting dates without computing the cloud cover first, or despite having seen the high CC), but it also repeatedly produced syntactically illegal tool calls in our integration tests. GLM-5.2 is a reliable, self-hostable alternative that we tested successfully.
- Python 3.11+
uvinstalled- LLM service reachable from your machine
uv sync --all-extrasVisit https://documentation.dataspace.copernicus.eu/APIs/S3.html for instructions on how to obtain access keys for S3 downloads of satellite imagery from the Copernicus Data Space Ecosystem (CDSE). Set up the environment variables AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY in your .env file. (You can use the .env.example file as a template.)
In order to use the Copernicus WMTS service, one has to register at Copernicus Dashboard and create a configuration (e.g. using the "Full WMS template") in the "configuration utility". Once created, the instance ID can be found under "service endpoints" (see also the respective CDSE documentation). That ID has to go into our .env file:
...
SENTINEL_HUB_INSTANCE_ID=<your_instance_id>
...uv run streamlit run src/eomas_assistant/app/streamlit_app.pyIf uv is not installed, run directly from the local virtual environment:
Windows PowerShell:
$env:PYTHONPATH='src'; .\.venv\Scripts\python.exe -m streamlit run src/eomas_assistant/app/streamlit_app.pyLinux/Unix (bash/zsh):
PYTHONPATH=src ./.venv/bin/python -m streamlit run src/eomas_assistant/app/streamlit_app.pyOpen the shown local URL in your browser.
uv run pytestOptional quality checks:
uv run ruff check .
uv run mypy srcWe have initially sketched a multi-agent system architecture for EOMAS, which is
shown in the following diagram:

Our current implementation is a simplified version of this architecture, and we plan to further simplify it towards a single-agent system. We found that a MAS is not a good fit for this use case, where we would have to introduce a lot more communication between the agents in order to organize the responsibilities and not lose information.
The LangGraph pipeline compiled in AgentWorkflow._build_graph() follows this control flow:
flowchart TD;
__start__([<p>__start__</p>]):::first
orchestrator(orchestrator)
conversation(conversation)
geography(geography)
eo_imagery(eo_imagery)
eo_imagery_tools(eo_imagery_tools)
evaluator(evaluator)
unsupported(unsupported)
error(error)
__end__([<p>__end__</p>]):::last
__start__ --> orchestrator;
conversation --> evaluator;
eo_imagery -. tools .-> eo_imagery_tools;
eo_imagery -. done .-> evaluator;
eo_imagery_tools --> eo_imagery;
error --> evaluator;
evaluator -. approved .-> __end__;
evaluator -. retry .-> orchestrator;
geography -.-> eo_imagery;
geography -. done .-> evaluator;
orchestrator -.-> conversation;
orchestrator -.-> error;
orchestrator -.-> geography;
orchestrator -.-> unsupported;
unsupported --> evaluator;
classDef default fill:#f2f0ff,line-height:1.2
classDef first fill-opacity:0
classDef last fill:#bfb6fc
The state passed through the graph comprises the user query, message history, and incremental agent outputs:
- The
OrchestratorAgentcreates theplanto answer the user request and routes the request to a fitting agent. - The
GeographyAgentwrites the user-facing response and, when successful, the resolvedgeo_location. - The
EOImageryAgentcreates aAssetCatalogwith the STAC information based on the above request analysis. - Then, it creates a
DataRequestwith the necessary information for the WMTS imagery overlay. - Several agents may augment the
responsewith things (text, map, tabular information for plotting etc.) to be rendered in the UI. - The
EvaluatorAgentis responsible for theevaluationpart of the state, which is used to determine whether the request has been successfully answered or whether it should be re-routed.
Ideally, the app would configure/know how big the LLM context window is, compact/trim message history, and detect when e.g. a tool call result or message history is too big. As it stands now, depending on the LLM provider, it may cut off exceeding tokens silently and output quality deteriorates.
As already stated above, we found that a multi-agent system (MAS) is not only not necessary for this prototype, but in fact counterproductive. The initial MAS approach causes more LLM calls (higher computational cost, increased latency) than necessary, without providing benefits in terms of modularity or maintainability. As it stands, we would have to introduce a lot more communication between the agents in order to organize the responsibilities and not lose information.
The prototype caches downloaded STAC frames in a "cache" directory, but this cache is never cleared automatically, which may lead to excessive disk usage over time. (Just remove the directory manually as you please.) Files are touched on use already, so it would not be hard to implement a cache eviction policy.
The Titiler integration was an experimental feature that was developed before
the EO imagery agent could actually merge and warp all necessary image frames
for a given user request. As a result of larger code changes, it was temporarily
disabled, although it would not be much work to get running again. The merged
images would have to be saved to disk before being served by Titiler. (Note
that rasterio.merge.merge() either returns the merged array or writes it to
disk, so it would have to be loaded from that file again also for analysis.)
