Visitar URL original
Markdown support enhancements (`output=md`) (#39) · serpapi/serpapi-python@9f79696 · GitHub
Skip to content

Commit 9f79696

Browse files
Markdown support enhancements (output=md) (#39)
* feat: add support for markdown output (output=md param) * tests: add tests for output formats (json, md, html) * doc: readme updates for output=md * tests: remove google events test
1 parent 1671cb3 commit 9f79696

5 files changed

Lines changed: 75 additions & 15 deletions

File tree

‎README.md‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,22 @@ Documentation is [available on Read the Docs](https://serpapi-python.readthedocs
7979

8080
Change history is [available on GitHub](https://github.com/serpapi/serpapi-python/blob/master/HISTORY.md).
8181

82+
### Markdown output for AI agents
83+
84+
For AI agents and LLM workflows, use `output="md"`. It returns search results with clean headings, links, and tables while using roughly half the tokens of JSON on average.
85+
86+
```python
87+
markdown = client.search({
88+
"engine": "google",
89+
"q": "coffee",
90+
"output": "md",
91+
})
92+
```
93+
94+
Markdown works across SerpApi APIs and is returned as a plain Python string. See [Markdown Output for AI Agents](https://serpapi.com/markdown-output.md) for details.
95+
96+
Raw HTML is also available with `output="html"` and is returned as a plain Python string.
97+
8298
## Basic Examples in Python
8399

84100
### Search Bing

‎serpapi/core.py‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ def __repr__(self):
3535
return "<SerpApi Client>"
3636

3737
def search(self, params: dict = None, **kwargs):
38-
"""Fetch a page of results from SerpApi. Returns a :class:`SerpResults <serpapi.client.SerpResults>` object, or unicode text (*e.g.* if ``'output': 'html'`` was passed).
38+
"""Fetch a page of results from SerpApi. Returns a :class:`SerpResults <serpapi.client.SerpResults>` object for JSON responses, or unicode text for HTML and Markdown responses.
3939
4040
The following three calls are equivalent:
4141
@@ -56,7 +56,7 @@ def search(self, params: dict = None, **kwargs):
5656
5757
:param q: typically, this is the parameter for the search engine query.
5858
:param engine: the search engine to use. Defaults to ``google``.
59-
:param output: the output format desired (``html`` or ``json``). Defaults to ``json``.
59+
:param output: the output format desired (``html``, ``json``, or ``md``). Defaults to ``json``.
6060
:param api_key: the API Key to use for SerpApi.com.
6161
:param **: any additional parameters to pass to the API.
6262
@@ -84,7 +84,7 @@ def search_archive(self, params: dict = None, **kwargs):
8484
8585
:param search_id: the Search ID of the search to retrieve from the archive.
8686
:param api_key: the API Key to use for SerpApi.com.
87-
:param output: the output format desired (``html`` or ``json``). Defaults to ``json``.
87+
:param output: the output format desired (``html``, ``json``, or ``md``). Defaults to ``json``.
8888
:param **: any additional parameters to pass to the API.
8989
9090
**Learn more**: https://serpapi.com/search-archive-api

‎serpapi/models.py‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,10 @@ def from_http_response(cls, r, *, client=None):
9292
Otherwise, the raw text (as a properly decoded unicode string) is returned.
9393
"""
9494

95+
content_type = r.headers.get("Content-Type", "").split(";", 1)[0].lower()
96+
if content_type in {"text/html", "text/markdown"}:
97+
return r.text
98+
9599
try:
96100
cls = cls(r.json(), client=client)
97101

‎tests/example_search_google_events_test.py‎

Lines changed: 0 additions & 12 deletions
This file was deleted.

‎tests/test_output_formats.py‎

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
from unittest.mock import Mock
2+
3+
import pytest
4+
import requests
5+
6+
import serpapi
7+
8+
9+
def response(content_type, content):
10+
response = requests.Response()
11+
response.status_code = 200
12+
response.headers["Content-Type"] = f"{content_type}; charset=utf-8"
13+
response._content = content.encode("utf-8")
14+
return response
15+
16+
17+
def test_search_json_returns_serp_results():
18+
client = serpapi.Client(api_key="test-api-key")
19+
client.session.request = Mock(
20+
return_value=response(
21+
"application/json",
22+
'{"search_metadata": {"id": "search-123"}}',
23+
)
24+
)
25+
26+
result = client.search(engine="google", q="Coffee", output="json")
27+
28+
assert isinstance(result, serpapi.SerpResults)
29+
assert result["search_metadata"]["id"] == "search-123"
30+
assert result.client is client
31+
_, request_kwargs = client.session.request.call_args
32+
assert request_kwargs["params"]["output"] == "json"
33+
34+
35+
@pytest.mark.parametrize(
36+
("output", "content_type"),
37+
[
38+
("html", "text/html"),
39+
("md", "text/markdown"),
40+
],
41+
)
42+
def test_search_text_outputs_return_raw_text(output, content_type):
43+
content = '{"looks": "like JSON"}'
44+
client = serpapi.Client(api_key="test-api-key")
45+
client.session.request = Mock(return_value=response(content_type, content))
46+
47+
result = client.search(engine="google", q="Coffee", output=output)
48+
49+
assert result == content
50+
assert isinstance(result, str)
51+
_, request_kwargs = client.session.request.call_args
52+
assert request_kwargs["params"]["output"] == output

0 commit comments

Comments
 (0)