diffbot-python 0.2.1__py3-none-any.whl → 0.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- diffbot/__init__.py +29 -4
- diffbot/ask.py +156 -9
- diffbot/cli/__init__.py +43 -12
- diffbot/client.py +68 -7
- diffbot/kg.py +5 -5
- diffbot_python-0.3.0.dist-info/METADATA +54 -0
- {diffbot_python-0.2.1.dist-info → diffbot_python-0.3.0.dist-info}/RECORD +10 -10
- diffbot_python-0.2.1.dist-info/METADATA +0 -266
- {diffbot_python-0.2.1.dist-info → diffbot_python-0.3.0.dist-info}/WHEEL +0 -0
- {diffbot_python-0.2.1.dist-info → diffbot_python-0.3.0.dist-info}/entry_points.txt +0 -0
- {diffbot_python-0.2.1.dist-info → diffbot_python-0.3.0.dist-info}/licenses/LICENSE +0 -0
diffbot/__init__.py
CHANGED
|
@@ -2,14 +2,38 @@
|
|
|
2
2
|
diffbot - Python client library for the Diffbot APIs.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
import warnings as _warnings
|
|
5
6
|
from importlib.metadata import PackageNotFoundError, version as _version
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
def _installed_version(dist: str):
|
|
9
|
+
try:
|
|
10
|
+
return _version(dist)
|
|
11
|
+
except PackageNotFoundError:
|
|
12
|
+
return None
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# The distribution was renamed from diffbot-python to diffbot. The final
|
|
16
|
+
# diffbot-python release ships this same code (see legacy/diffbot-python), so
|
|
17
|
+
# fall back to its version when that's what is installed.
|
|
18
|
+
_legacy_version = _installed_version("diffbot-python")
|
|
19
|
+
# "0.0.0" when not installed (e.g. running from a source tree).
|
|
20
|
+
__version__ = _installed_version("diffbot") or _legacy_version or "0.0.0"
|
|
21
|
+
|
|
22
|
+
# Both distributions ship this module, so a diffbot-python install (alone, or
|
|
23
|
+
# left over next to diffbot) should migrate. FutureWarning, unlike
|
|
24
|
+
# DeprecationWarning, is shown by default wherever the import happens.
|
|
25
|
+
if _legacy_version is not None:
|
|
26
|
+
_warnings.warn(
|
|
27
|
+
"diffbot-python has been renamed to diffbot and will receive no further "
|
|
28
|
+
"updates. Run `pip uninstall diffbot-python && pip install "
|
|
29
|
+
"--force-reinstall diffbot` and replace diffbot-python with diffbot in "
|
|
30
|
+
"your requirements.",
|
|
31
|
+
FutureWarning,
|
|
32
|
+
stacklevel=2,
|
|
33
|
+
)
|
|
11
34
|
|
|
12
35
|
from ._auth import resolve_token
|
|
36
|
+
from .ask import json_schema_format
|
|
13
37
|
from .client import Diffbot, DiffbotAsync
|
|
14
38
|
from .crawl import CrawlEvent, CrawlEventType
|
|
15
39
|
from .errors import (
|
|
@@ -26,6 +50,7 @@ __all__ = [
|
|
|
26
50
|
"Diffbot",
|
|
27
51
|
"DiffbotAsync",
|
|
28
52
|
"resolve_token",
|
|
53
|
+
"json_schema_format",
|
|
29
54
|
"CrawlEvent",
|
|
30
55
|
"CrawlEventType",
|
|
31
56
|
"Ontology",
|
diffbot/ask.py
CHANGED
|
@@ -1,15 +1,75 @@
|
|
|
1
1
|
"""Diffbot LLM RAG API: stream a chat completion."""
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
-
|
|
4
|
+
import re
|
|
5
|
+
from typing import TYPE_CHECKING, Any, AsyncIterator, Dict, Iterator, List, Optional
|
|
6
|
+
|
|
7
|
+
from .errors import ValidationError
|
|
5
8
|
|
|
6
9
|
if TYPE_CHECKING:
|
|
7
10
|
from .client import Diffbot, DiffbotAsync
|
|
8
11
|
|
|
12
|
+
MODEL = "diffbot-small-xl"
|
|
13
|
+
|
|
14
|
+
#: `response_format` types accepted by the Diffbot LLM endpoint.
|
|
15
|
+
RESPONSE_FORMAT_TYPES = ("text", "json_object", "json_schema")
|
|
16
|
+
|
|
17
|
+
# The RAG loop may prefix its final answer with a think block, and the model
|
|
18
|
+
# occasionally wraps JSON in a markdown fence despite being told not to.
|
|
19
|
+
_THINK_BLOCK = re.compile(r"<think>.*?</think>", re.DOTALL)
|
|
20
|
+
_JSON_FENCE = re.compile(r"^```(?:json)?\s*|\s*```$", re.MULTILINE)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def json_schema_format(schema: Dict[str, Any], *, name: str = "response") -> Dict[str, Any]:
|
|
24
|
+
"""Build a ``response_format`` value that constrains output to ``schema``.
|
|
25
|
+
|
|
26
|
+
The endpoint requires the schema nested under ``json_schema.schema``; passing
|
|
27
|
+
it anywhere else is ignored server-side without an error, so prefer this
|
|
28
|
+
helper over hand-building the dict.
|
|
29
|
+
|
|
30
|
+
Example:
|
|
31
|
+
>>> json_schema_format({"type": "object", "properties": {"city": {"type": "string"}}})
|
|
32
|
+
{'type': 'json_schema', 'json_schema': {'name': 'response', 'schema': {...}}}
|
|
33
|
+
"""
|
|
34
|
+
if not isinstance(schema, dict):
|
|
35
|
+
raise ValidationError("schema must be a JSON Schema dict")
|
|
36
|
+
return {"type": "json_schema", "json_schema": {"name": name, "schema": schema}}
|
|
37
|
+
|
|
9
38
|
|
|
10
|
-
def
|
|
39
|
+
def _validate_response_format(response_format: Optional[Dict[str, Any]]) -> None:
|
|
40
|
+
"""Reject shapes the endpoint would accept but silently not enforce."""
|
|
41
|
+
if response_format is None:
|
|
42
|
+
return
|
|
43
|
+
if not isinstance(response_format, dict):
|
|
44
|
+
raise ValidationError("response_format must be a dict")
|
|
45
|
+
|
|
46
|
+
fmt_type = response_format.get("type", "text")
|
|
47
|
+
if fmt_type not in RESPONSE_FORMAT_TYPES:
|
|
48
|
+
raise ValidationError(
|
|
49
|
+
f"response_format type must be one of {', '.join(RESPONSE_FORMAT_TYPES)}; got {fmt_type!r}"
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
if fmt_type == "json_schema":
|
|
53
|
+
json_schema = response_format.get("json_schema")
|
|
54
|
+
if not isinstance(json_schema, dict) or "schema" not in json_schema:
|
|
55
|
+
raise ValidationError(
|
|
56
|
+
'response_format {"type": "json_schema"} requires the schema nested as '
|
|
57
|
+
'{"json_schema": {"schema": {...}}}. Without it the server returns 200 and '
|
|
58
|
+
"ignores the constraint. Use diffbot.json_schema_format(schema) to build it."
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _build_payload(
|
|
63
|
+
client: Any,
|
|
64
|
+
messages: List[Dict[str, str]],
|
|
65
|
+
*,
|
|
66
|
+
response_format: Optional[Dict[str, Any]] = None,
|
|
67
|
+
) -> tuple:
|
|
68
|
+
_validate_response_format(response_format)
|
|
11
69
|
headers = {"Authorization": f"Bearer {client.token}"}
|
|
12
|
-
payload = {"model":
|
|
70
|
+
payload: Dict[str, Any] = {"model": MODEL, "messages": messages, "stream": True}
|
|
71
|
+
if response_format is not None:
|
|
72
|
+
payload["response_format"] = response_format
|
|
13
73
|
return headers, payload
|
|
14
74
|
|
|
15
75
|
|
|
@@ -24,9 +84,68 @@ def _parse_chunk(line: str):
|
|
|
24
84
|
return None
|
|
25
85
|
|
|
26
86
|
|
|
27
|
-
def
|
|
28
|
-
|
|
29
|
-
|
|
87
|
+
def _extract_json(text: str) -> Any:
|
|
88
|
+
"""Parse the model's final answer as JSON, tolerating think blocks and fences."""
|
|
89
|
+
cleaned = _THINK_BLOCK.sub("", text).strip()
|
|
90
|
+
cleaned = _JSON_FENCE.sub("", cleaned).strip()
|
|
91
|
+
|
|
92
|
+
try:
|
|
93
|
+
return json.loads(cleaned)
|
|
94
|
+
except json.JSONDecodeError:
|
|
95
|
+
pass
|
|
96
|
+
|
|
97
|
+
# Fall back to the outermost object or array span in the response.
|
|
98
|
+
spans = []
|
|
99
|
+
for opener, closer in (("{", "}"), ("[", "]")):
|
|
100
|
+
start, end = cleaned.find(opener), cleaned.rfind(closer)
|
|
101
|
+
if start != -1 and end > start:
|
|
102
|
+
spans.append((start, cleaned[start : end + 1]))
|
|
103
|
+
for _, span in sorted(spans):
|
|
104
|
+
try:
|
|
105
|
+
return json.loads(span)
|
|
106
|
+
except json.JSONDecodeError:
|
|
107
|
+
continue
|
|
108
|
+
|
|
109
|
+
raise ValidationError(f"could not parse JSON from the model response: {text[:200]!r}")
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
#: Schema used when the caller wants JSON but has no shape in mind. This goes
|
|
113
|
+
#: through the json_schema path rather than {"type": "json_object"} on purpose:
|
|
114
|
+
#: json_object applies no server-side grammar, so the RAG loop's internal
|
|
115
|
+
#: <functioncall> JSON satisfies it and gets returned as the final answer. That
|
|
116
|
+
#: is reproducible whenever the request carries a system message.
|
|
117
|
+
ANY_OBJECT_SCHEMA = {"type": "object"}
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _resolve_format(
|
|
121
|
+
schema: Optional[Dict[str, Any]],
|
|
122
|
+
response_format: Optional[Dict[str, Any]],
|
|
123
|
+
) -> Dict[str, Any]:
|
|
124
|
+
if schema is not None and response_format is not None:
|
|
125
|
+
raise ValidationError("pass either schema or response_format, not both")
|
|
126
|
+
if response_format is not None:
|
|
127
|
+
return response_format
|
|
128
|
+
return json_schema_format(schema if schema is not None else ANY_OBJECT_SCHEMA)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _check_tool_call_leak(parsed: Any) -> Any:
|
|
132
|
+
"""Catch the internal tool call surfacing as the answer (see ANY_OBJECT_SCHEMA)."""
|
|
133
|
+
if isinstance(parsed, dict) and parsed.get("name") == "functioncall" and "arguments" in parsed:
|
|
134
|
+
raise ValidationError(
|
|
135
|
+
"the model returned its internal tool call instead of an answer; this happens with "
|
|
136
|
+
'response_format {"type": "json_object"} because the server applies no grammar to it. '
|
|
137
|
+
"Pass a schema instead."
|
|
138
|
+
)
|
|
139
|
+
return parsed
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def ask(
|
|
143
|
+
client: "Diffbot",
|
|
144
|
+
messages: List[Dict[str, str]],
|
|
145
|
+
*,
|
|
146
|
+
response_format: Optional[Dict[str, Any]] = None,
|
|
147
|
+
) -> Iterator[str]:
|
|
148
|
+
headers, payload = _build_payload(client, messages, response_format=response_format)
|
|
30
149
|
with client._http.stream("POST", client.llm_url, headers=headers, json=payload) as response:
|
|
31
150
|
client._raise_for_status(response)
|
|
32
151
|
for line in response.iter_lines():
|
|
@@ -36,9 +155,13 @@ def ask(client: "Diffbot", messages: List[Dict[str, str]]) -> Iterator[str]:
|
|
|
36
155
|
yield content
|
|
37
156
|
|
|
38
157
|
|
|
39
|
-
async def ask_async(
|
|
40
|
-
|
|
41
|
-
|
|
158
|
+
async def ask_async(
|
|
159
|
+
client: "DiffbotAsync",
|
|
160
|
+
messages: List[Dict[str, str]],
|
|
161
|
+
*,
|
|
162
|
+
response_format: Optional[Dict[str, Any]] = None,
|
|
163
|
+
) -> AsyncIterator[str]:
|
|
164
|
+
headers, payload = _build_payload(client, messages, response_format=response_format)
|
|
42
165
|
async with client._http.stream("POST", client.llm_url, headers=headers, json=payload) as response:
|
|
43
166
|
client._raise_for_status(response)
|
|
44
167
|
async for line in response.aiter_lines():
|
|
@@ -46,3 +169,27 @@ async def ask_async(client: "DiffbotAsync", messages: List[Dict[str, str]]) -> A
|
|
|
46
169
|
content = _parse_chunk(line)
|
|
47
170
|
if content:
|
|
48
171
|
yield content
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def ask_json(
|
|
175
|
+
client: "Diffbot",
|
|
176
|
+
messages: List[Dict[str, str]],
|
|
177
|
+
schema: Optional[Dict[str, Any]] = None,
|
|
178
|
+
*,
|
|
179
|
+
response_format: Optional[Dict[str, Any]] = None,
|
|
180
|
+
) -> Any:
|
|
181
|
+
fmt = _resolve_format(schema, response_format)
|
|
182
|
+
text = "".join(ask(client, messages, response_format=fmt))
|
|
183
|
+
return _check_tool_call_leak(_extract_json(text))
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
async def ask_json_async(
|
|
187
|
+
client: "DiffbotAsync",
|
|
188
|
+
messages: List[Dict[str, str]],
|
|
189
|
+
schema: Optional[Dict[str, Any]] = None,
|
|
190
|
+
*,
|
|
191
|
+
response_format: Optional[Dict[str, Any]] = None,
|
|
192
|
+
) -> Any:
|
|
193
|
+
fmt = _resolve_format(schema, response_format)
|
|
194
|
+
chunks = [chunk async for chunk in ask_async(client, messages, response_format=fmt)]
|
|
195
|
+
return _check_tool_call_leak(_extract_json("".join(chunks)))
|
diffbot/cli/__init__.py
CHANGED
|
@@ -11,7 +11,15 @@ from rich.markdown import Markdown
|
|
|
11
11
|
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
12
12
|
from datetime import datetime
|
|
13
13
|
|
|
14
|
-
from diffbot import
|
|
14
|
+
from diffbot import (
|
|
15
|
+
__version__,
|
|
16
|
+
CrawlEventType,
|
|
17
|
+
AuthError,
|
|
18
|
+
ExtractionError,
|
|
19
|
+
APIError,
|
|
20
|
+
ValidationError,
|
|
21
|
+
json_schema_format,
|
|
22
|
+
)
|
|
15
23
|
|
|
16
24
|
from ._common import get_client
|
|
17
25
|
|
|
@@ -28,6 +36,7 @@ def print_markdown(text):
|
|
|
28
36
|
|
|
29
37
|
|
|
30
38
|
@click.group()
|
|
39
|
+
@click.version_option(__version__, "-V", "--version", prog_name="db")
|
|
31
40
|
def main():
|
|
32
41
|
"""
|
|
33
42
|
Diffbot 🤖 Structure the world's knowledge.
|
|
@@ -85,10 +94,29 @@ def extract(url, output, fmt, api):
|
|
|
85
94
|
@click.argument("prompt")
|
|
86
95
|
@click.option("-o", "--output", type=click.Path(), help="Write output to file instead of stdout")
|
|
87
96
|
@click.option("--json", "as_json", is_flag=True, help="Output from LLM as JSON")
|
|
88
|
-
|
|
97
|
+
@click.option(
|
|
98
|
+
"--schema",
|
|
99
|
+
"schema_path",
|
|
100
|
+
type=click.Path(exists=True, dir_okay=False),
|
|
101
|
+
help="JSON Schema file constraining the output (implies --json)",
|
|
102
|
+
)
|
|
103
|
+
def ask(prompt: str, output: str = None, as_json: bool = False, schema_path: str = None):
|
|
89
104
|
"""Ask a question to the Diffbot LLM"""
|
|
90
105
|
db = get_client()
|
|
91
106
|
stdin_content = sys.stdin.read() if not sys.stdin.isatty() else None
|
|
107
|
+
|
|
108
|
+
response_format = None
|
|
109
|
+
if schema_path:
|
|
110
|
+
as_json = True
|
|
111
|
+
try:
|
|
112
|
+
with open(schema_path) as f:
|
|
113
|
+
response_format = json_schema_format(json.load(f))
|
|
114
|
+
except json.JSONDecodeError as e:
|
|
115
|
+
click.echo(f"Error: {schema_path} is not valid JSON: {e}", err=True)
|
|
116
|
+
raise click.Abort()
|
|
117
|
+
|
|
118
|
+
# Without --schema, response_format stays None and ask_json picks its own
|
|
119
|
+
# permissive default; --json alone must not become {"type": "json_object"}.
|
|
92
120
|
interactive_mode = is_interactive and not output and not as_json
|
|
93
121
|
|
|
94
122
|
messages = [
|
|
@@ -99,9 +127,6 @@ def ask(prompt: str, output: str = None, as_json: bool = False):
|
|
|
99
127
|
{"role": "user", "content": prompt},
|
|
100
128
|
]
|
|
101
129
|
|
|
102
|
-
if as_json:
|
|
103
|
-
messages[1]["content"] += "\nReturn the output as a JSON object. Do not include any other text outside of the JSON object."
|
|
104
|
-
|
|
105
130
|
if stdin_content:
|
|
106
131
|
messages[1]["content"] = f"<input>{stdin_content}</input>\n" + messages[1]["content"]
|
|
107
132
|
|
|
@@ -136,17 +161,20 @@ def ask(prompt: str, output: str = None, as_json: bool = False):
|
|
|
136
161
|
else:
|
|
137
162
|
response = ""
|
|
138
163
|
if output:
|
|
139
|
-
|
|
140
|
-
response
|
|
164
|
+
if as_json:
|
|
165
|
+
response = json.dumps(
|
|
166
|
+
db.ask_json(messages, response_format=response_format), indent=2
|
|
167
|
+
)
|
|
168
|
+
else:
|
|
169
|
+
for chunk in db.ask(messages):
|
|
170
|
+
response += chunk
|
|
141
171
|
with open(output, "w") as f:
|
|
142
172
|
f.write(response)
|
|
143
173
|
click.echo(f"Output written to {output}")
|
|
144
174
|
elif as_json:
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
buffer = buffer[buffer.find("{") : buffer.rfind("}") + 1]
|
|
149
|
-
sys.stdout.write(buffer)
|
|
175
|
+
sys.stdout.write(
|
|
176
|
+
json.dumps(db.ask_json(messages, response_format=response_format), indent=2)
|
|
177
|
+
)
|
|
150
178
|
else:
|
|
151
179
|
for chunk in db.ask(messages):
|
|
152
180
|
sys.stdout.write(chunk)
|
|
@@ -155,6 +183,9 @@ def ask(prompt: str, output: str = None, as_json: bool = False):
|
|
|
155
183
|
except AuthError:
|
|
156
184
|
click.echo("Error: Invalid or unauthorized API token.", err=True)
|
|
157
185
|
raise click.Abort()
|
|
186
|
+
except ValidationError as e:
|
|
187
|
+
click.echo(f"Error: {e}", err=True)
|
|
188
|
+
raise click.Abort()
|
|
158
189
|
except APIError as e:
|
|
159
190
|
click.echo(f"API error {e.status_code}: {e.message or e.body}", err=True)
|
|
160
191
|
raise click.Abort()
|
diffbot/client.py
CHANGED
|
@@ -9,7 +9,12 @@ import httpx
|
|
|
9
9
|
from . import __version__
|
|
10
10
|
from .errors import APIError, AuthError, RateLimitError, ValidationError
|
|
11
11
|
from .extract import extract as _extract, extract_async as _extract_async
|
|
12
|
-
from .ask import
|
|
12
|
+
from .ask import (
|
|
13
|
+
ask as _ask,
|
|
14
|
+
ask_async as _ask_async,
|
|
15
|
+
ask_json as _ask_json,
|
|
16
|
+
ask_json_async as _ask_json_async,
|
|
17
|
+
)
|
|
13
18
|
from .crawl import (
|
|
14
19
|
CrawlEvent,
|
|
15
20
|
crawl as _crawl,
|
|
@@ -22,6 +27,8 @@ from .crawl import (
|
|
|
22
27
|
crawl_list_jobs_async as _crawl_list_jobs_async,
|
|
23
28
|
)
|
|
24
29
|
from .kg import (
|
|
30
|
+
KG_DQL_ENDPOINT,
|
|
31
|
+
KG_ONTOLOGY_ENDPOINT,
|
|
25
32
|
dql as _dql,
|
|
26
33
|
dql_async as _dql_async,
|
|
27
34
|
dql_fetch_ontology as _dql_fetch_ontology,
|
|
@@ -67,6 +74,8 @@ class Diffbot:
|
|
|
67
74
|
crawler_url: str = CRAWL_BASE,
|
|
68
75
|
web_search_url: str = WEB_SEARCH_BASE,
|
|
69
76
|
nlp_url: str = NLP_BASE,
|
|
77
|
+
dql_url: str = KG_DQL_ENDPOINT,
|
|
78
|
+
ontology_url: str = KG_ONTOLOGY_ENDPOINT,
|
|
70
79
|
transport: Optional[httpx.BaseTransport] = None,
|
|
71
80
|
):
|
|
72
81
|
if not token:
|
|
@@ -77,6 +86,8 @@ class Diffbot:
|
|
|
77
86
|
self.crawler_url = crawler_url
|
|
78
87
|
self.web_search_url = web_search_url
|
|
79
88
|
self.nlp_url = nlp_url
|
|
89
|
+
self.dql_url = dql_url
|
|
90
|
+
self.ontology_url = ontology_url
|
|
80
91
|
self._http = httpx.Client(
|
|
81
92
|
timeout=timeout,
|
|
82
93
|
headers={"User-Agent": f"diffbot-python/{__version__}"},
|
|
@@ -112,9 +123,32 @@ class Diffbot:
|
|
|
112
123
|
"""Extract structured content from a URL. Returns the raw Diffbot API response."""
|
|
113
124
|
return _extract(self, url, api=api, fmt=fmt)
|
|
114
125
|
|
|
115
|
-
def ask(
|
|
116
|
-
|
|
117
|
-
|
|
126
|
+
def ask(
|
|
127
|
+
self,
|
|
128
|
+
messages: List[Dict[str, str]],
|
|
129
|
+
*,
|
|
130
|
+
response_format: Optional[Dict[str, Any]] = None,
|
|
131
|
+
) -> Iterator[str]:
|
|
132
|
+
"""Stream a response from the Diffbot LLM RAG API.
|
|
133
|
+
|
|
134
|
+
Pass ``response_format`` to constrain the output, e.g.
|
|
135
|
+
``{"type": "json_object"}`` or ``diffbot.json_schema_format(schema)``.
|
|
136
|
+
"""
|
|
137
|
+
yield from _ask(self, messages, response_format=response_format)
|
|
138
|
+
|
|
139
|
+
def ask_json(
|
|
140
|
+
self,
|
|
141
|
+
messages: List[Dict[str, str]],
|
|
142
|
+
schema: Optional[Dict[str, Any]] = None,
|
|
143
|
+
*,
|
|
144
|
+
response_format: Optional[Dict[str, Any]] = None,
|
|
145
|
+
) -> Any:
|
|
146
|
+
"""Ask the Diffbot LLM and return the answer parsed as JSON.
|
|
147
|
+
|
|
148
|
+
With ``schema`` (a JSON Schema dict) the model is constrained to it by
|
|
149
|
+
grammar; without one it falls back to unconstrained JSON output.
|
|
150
|
+
"""
|
|
151
|
+
return _ask_json(self, messages, schema, response_format=response_format)
|
|
118
152
|
|
|
119
153
|
def crawl(self, site: str, **kwargs: Any) -> Iterator[CrawlEvent]:
|
|
120
154
|
"""Start a crawl job."""
|
|
@@ -194,6 +228,8 @@ class DiffbotAsync:
|
|
|
194
228
|
crawler_url: str = CRAWL_BASE,
|
|
195
229
|
web_search_url: str = WEB_SEARCH_BASE,
|
|
196
230
|
nlp_url: str = NLP_BASE,
|
|
231
|
+
dql_url: str = KG_DQL_ENDPOINT,
|
|
232
|
+
ontology_url: str = KG_ONTOLOGY_ENDPOINT,
|
|
197
233
|
transport: Optional[httpx.AsyncBaseTransport] = None,
|
|
198
234
|
):
|
|
199
235
|
if not token:
|
|
@@ -204,6 +240,8 @@ class DiffbotAsync:
|
|
|
204
240
|
self.crawler_url = crawler_url
|
|
205
241
|
self.web_search_url = web_search_url
|
|
206
242
|
self.nlp_url = nlp_url
|
|
243
|
+
self.dql_url = dql_url
|
|
244
|
+
self.ontology_url = ontology_url
|
|
207
245
|
self._http = httpx.AsyncClient(
|
|
208
246
|
timeout=timeout,
|
|
209
247
|
headers={"User-Agent": f"diffbot-python/{__version__}"},
|
|
@@ -239,11 +277,34 @@ class DiffbotAsync:
|
|
|
239
277
|
"""Extract structured content from a URL. Returns the raw Diffbot API response."""
|
|
240
278
|
return await _extract_async(self, url, api=api, fmt=fmt)
|
|
241
279
|
|
|
242
|
-
async def ask(
|
|
243
|
-
|
|
244
|
-
|
|
280
|
+
async def ask(
|
|
281
|
+
self,
|
|
282
|
+
messages: List[Dict[str, str]],
|
|
283
|
+
*,
|
|
284
|
+
response_format: Optional[Dict[str, Any]] = None,
|
|
285
|
+
) -> AsyncIterator[str]:
|
|
286
|
+
"""Stream a response from the Diffbot LLM RAG API.
|
|
287
|
+
|
|
288
|
+
Pass ``response_format`` to constrain the output, e.g.
|
|
289
|
+
``{"type": "json_object"}`` or ``diffbot.json_schema_format(schema)``.
|
|
290
|
+
"""
|
|
291
|
+
async for chunk in _ask_async(self, messages, response_format=response_format):
|
|
245
292
|
yield chunk
|
|
246
293
|
|
|
294
|
+
async def ask_json(
|
|
295
|
+
self,
|
|
296
|
+
messages: List[Dict[str, str]],
|
|
297
|
+
schema: Optional[Dict[str, Any]] = None,
|
|
298
|
+
*,
|
|
299
|
+
response_format: Optional[Dict[str, Any]] = None,
|
|
300
|
+
) -> Any:
|
|
301
|
+
"""Ask the Diffbot LLM and return the answer parsed as JSON.
|
|
302
|
+
|
|
303
|
+
With ``schema`` (a JSON Schema dict) the model is constrained to it by
|
|
304
|
+
grammar; without one it falls back to unconstrained JSON output.
|
|
305
|
+
"""
|
|
306
|
+
return await _ask_json_async(self, messages, schema, response_format=response_format)
|
|
307
|
+
|
|
247
308
|
async def crawl(self, site: str, **kwargs: Any) -> AsyncIterator[CrawlEvent]:
|
|
248
309
|
"""Start a crawl job. Pass watch=True to poll until completion and yield URL_PROCESSED events."""
|
|
249
310
|
async for event in _crawl_async(self, site, **kwargs):
|
diffbot/kg.py
CHANGED
|
@@ -51,7 +51,7 @@ def dql(
|
|
|
51
51
|
raw: bool = False,
|
|
52
52
|
) -> Union[Dict[str, Any], bytes]:
|
|
53
53
|
params = _build_dql_params(client, query, size, from_, format, filter, exportspec, extra)
|
|
54
|
-
response = client._http.get(
|
|
54
|
+
response = client._http.get(client.dql_url, params=params)
|
|
55
55
|
client._raise_for_status(response)
|
|
56
56
|
return response.content if raw else response.json()
|
|
57
57
|
|
|
@@ -69,7 +69,7 @@ async def dql_async(
|
|
|
69
69
|
raw: bool = False,
|
|
70
70
|
) -> Union[Dict[str, Any], bytes]:
|
|
71
71
|
params = _build_dql_params(client, query, size, from_, format, filter, exportspec, extra)
|
|
72
|
-
response = await client._http.get(
|
|
72
|
+
response = await client._http.get(client.dql_url, params=params)
|
|
73
73
|
client._raise_for_status(response)
|
|
74
74
|
return response.content if raw else response.json()
|
|
75
75
|
|
|
@@ -104,7 +104,7 @@ async def dql_parallel_async(
|
|
|
104
104
|
|
|
105
105
|
|
|
106
106
|
def dql_refresh_ontology(client: "Diffbot", dest: pathlib.Path) -> None:
|
|
107
|
-
response = client._http.get(
|
|
107
|
+
response = client._http.get(client.ontology_url)
|
|
108
108
|
client._raise_for_status(response)
|
|
109
109
|
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
110
110
|
dest.write_bytes(response.content)
|
|
@@ -116,13 +116,13 @@ def dql_fetch_ontology(client: "Diffbot") -> Ontology:
|
|
|
116
116
|
Performs no caching — the caller decides whether and where to hold onto the
|
|
117
117
|
result. Use :func:`dql_refresh_ontology` instead to persist raw bytes to disk.
|
|
118
118
|
"""
|
|
119
|
-
response = client._http.get(
|
|
119
|
+
response = client._http.get(client.ontology_url)
|
|
120
120
|
client._raise_for_status(response)
|
|
121
121
|
return Ontology.from_json(response.content)
|
|
122
122
|
|
|
123
123
|
|
|
124
124
|
async def dql_fetch_ontology_async(client: "DiffbotAsync") -> Ontology:
|
|
125
125
|
"""Async variant of :func:`dql_fetch_ontology`."""
|
|
126
|
-
response = await client._http.get(
|
|
126
|
+
response = await client._http.get(client.ontology_url)
|
|
127
127
|
client._raise_for_status(response)
|
|
128
128
|
return Ontology.from_json(response.content)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: diffbot-python
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: ⚠️ LEGACY — renamed to diffbot. Final release, no further updates: pip install diffbot
|
|
5
|
+
Project-URL: Homepage, https://pypi.org/project/diffbot/
|
|
6
|
+
Project-URL: Repository, https://github.com/diffbot/diffbot-python
|
|
7
|
+
Author-email: Jerome Choo <jerome@diffbot.com>, Mike Tung <miket@diffbot.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Development Status :: 7 - Inactive
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
13
|
+
Requires-Python: >=3.9
|
|
14
|
+
Requires-Dist: click>=8.1.0
|
|
15
|
+
Requires-Dist: httpx>=0.27.0
|
|
16
|
+
Requires-Dist: rich>=13.0.0
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
# ⚠️ LEGACY PACKAGE — `diffbot-python` is now `diffbot` ⚠️
|
|
20
|
+
|
|
21
|
+
> ## 🛑 Do not install `diffbot-python`. Install [`diffbot`](https://pypi.org/project/diffbot/) instead:
|
|
22
|
+
>
|
|
23
|
+
> ```bash
|
|
24
|
+
> pip uninstall diffbot-python
|
|
25
|
+
> pip install diffbot
|
|
26
|
+
> ```
|
|
27
|
+
>
|
|
28
|
+
> **This is the final release of `diffbot-python` (0.3.0). It will never be updated again**:
|
|
29
|
+
> no bug fixes, no new Diffbot API features, no security fixes.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
The Diffbot Python library and `db` CLI now live at
|
|
34
|
+
**[diffbot](https://pypi.org/project/diffbot/)**. The import name is unchanged
|
|
35
|
+
(`import diffbot`), so no code changes are needed.
|
|
36
|
+
|
|
37
|
+
This release contains the same code as `diffbot` 3.0.0 so existing installs keep
|
|
38
|
+
working, but it prints a warning on every import until you migrate.
|
|
39
|
+
|
|
40
|
+
## Migrating
|
|
41
|
+
|
|
42
|
+
1. Replace `diffbot-python` with `diffbot` in your `requirements.txt`, `pyproject.toml`, etc.
|
|
43
|
+
2. Uninstall the old package **before** installing the new one:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pip uninstall diffbot-python
|
|
47
|
+
pip install diffbot
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Both packages provide the same `diffbot` module, so uninstalling
|
|
51
|
+
`diffbot-python` after `diffbot` is installed deletes files that `diffbot`
|
|
52
|
+
needs. If that happened, run `pip install --force-reinstall diffbot`.
|
|
53
|
+
|
|
54
|
+
Source and issues: <https://github.com/diffbot/diffbot-python>.
|
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
diffbot/__init__.py,sha256=
|
|
1
|
+
diffbot/__init__.py,sha256=LAZr0P760URw34JXMIcN-2fTMIELq_-IUd1gj8KgVfI,1875
|
|
2
2
|
diffbot/_auth.py,sha256=dzNMWqIjtG3YtRBb_EKpPRiBxvZgogxvoE0ABLvzfOk,1251
|
|
3
|
-
diffbot/ask.py,sha256=
|
|
4
|
-
diffbot/client.py,sha256=
|
|
3
|
+
diffbot/ask.py,sha256=taLISU0BUHzi9SwESZmgDST8PUHoLGJnI02aHngljQM,7411
|
|
4
|
+
diffbot/client.py,sha256=k3orUmOv1RG_xHInyEt0bS0CIYyTOBW56ZDEMgvX8nc,13379
|
|
5
5
|
diffbot/crawl.py,sha256=iYMFmf7HKrbefJrGg14VnlfiBFLOE_Z1pfO4Rn_cDXc,8893
|
|
6
6
|
diffbot/errors.py,sha256=5-AceX5MyNVUhe9pvR_4rnQQmBhvLfwWmrRl7dRZUSg,1576
|
|
7
7
|
diffbot/extract.py,sha256=R9SVxaOi4FjHOQIX5ho_75OwaJ7VdX-mdAoV_UY-lrM,1452
|
|
8
|
-
diffbot/kg.py,sha256=
|
|
8
|
+
diffbot/kg.py,sha256=WPvy3B_ywdoM0uEmAgN7-3uOvf4aC4l2mjS2v_tWPN4,3925
|
|
9
9
|
diffbot/nlp.py,sha256=lZJW4MkjhVklIEM2OBfhc8LvresXTy7RsuImPteYsOA,1153
|
|
10
10
|
diffbot/ontology.py,sha256=GWD5m2rz7ECWY6RrO1TeN3kk3DYTgmEHz6gFKASMvAU,6283
|
|
11
11
|
diffbot/web_search.py,sha256=RSaEK0pdAdLgtilc500Mf2MNmJ1DmiYYmIlBQBWvOpE,1356
|
|
12
|
-
diffbot/cli/__init__.py,sha256=
|
|
12
|
+
diffbot/cli/__init__.py,sha256=uCngMioFdxTSeUrBxPrxepVqqwllAjXVr8W-zThcYaE,17435
|
|
13
13
|
diffbot/cli/__main__.py,sha256=5BjNuyet8AY-POwoF5rGt722rHQ7tJ0Vf0UFUfzzi-I,58
|
|
14
14
|
diffbot/cli/_common.py,sha256=_s8WRukzSX3krjJW6HJmT7_3hEjNyv1i_y45tm81b2A,654
|
|
15
15
|
diffbot/cli/dql.py,sha256=iIMaGysMWTScnbAHAdWei2iVUJpMV5lA1pk1E9uQg4s,11439
|
|
16
16
|
diffbot/cli/entities.py,sha256=tsHKexF0b6NnsoUEZJUS2rJidTIE8lt6xrcSenPbwtY,5630
|
|
17
17
|
diffbot/cli/ontology.py,sha256=cLuRQ0KTsOJnFCXdG_zIX9J1ODK8ThPznWVDDEpO1pg,2055
|
|
18
|
-
diffbot_python-0.
|
|
19
|
-
diffbot_python-0.
|
|
20
|
-
diffbot_python-0.
|
|
21
|
-
diffbot_python-0.
|
|
22
|
-
diffbot_python-0.
|
|
18
|
+
diffbot_python-0.3.0.dist-info/METADATA,sha256=ryJb4Ai_rjjbtmngp1o6cYnennDxK3dJWsokcKZ7-U0,1985
|
|
19
|
+
diffbot_python-0.3.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
20
|
+
diffbot_python-0.3.0.dist-info/entry_points.txt,sha256=FCxJhrbl7VNEsTK7zl7qYvXID7gQ1_wxhiw5_Vllb_M,40
|
|
21
|
+
diffbot_python-0.3.0.dist-info/licenses/LICENSE,sha256=UZlamI1XGeiG0Mit8dsHssNhOuMGKfmNOp5qpf1533w,1063
|
|
22
|
+
diffbot_python-0.3.0.dist-info/RECORD,,
|
|
@@ -1,266 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: diffbot-python
|
|
3
|
-
Version: 0.2.1
|
|
4
|
-
Summary: Python client library for Diffbot APIs
|
|
5
|
-
Project-URL: Homepage, https://github.com/diffbot/diffbot-python
|
|
6
|
-
Project-URL: Documentation, https://github.com/diffbot/diffbot-python#readme
|
|
7
|
-
Project-URL: Repository, https://github.com/diffbot/diffbot-python
|
|
8
|
-
Project-URL: Issues, https://github.com/diffbot/diffbot-python/issues
|
|
9
|
-
Author-email: Jerome Choo <jerome@diffbot.com>, Mike Tung <miket@diffbot.com>
|
|
10
|
-
License-Expression: MIT
|
|
11
|
-
License-File: LICENSE
|
|
12
|
-
Keywords: api-client,crawler,diffbot,extract,knowledge-graph,llm,nlp,web-scraping
|
|
13
|
-
Classifier: Development Status :: 3 - Alpha
|
|
14
|
-
Classifier: Intended Audience :: Developers
|
|
15
|
-
Classifier: Operating System :: OS Independent
|
|
16
|
-
Classifier: Programming Language :: Python :: 3
|
|
17
|
-
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
-
Classifier: Topic :: Internet :: WWW/HTTP
|
|
23
|
-
Classifier: Topic :: Internet :: WWW/HTTP :: Indexing/Search
|
|
24
|
-
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
25
|
-
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
26
|
-
Classifier: Topic :: Text Processing :: Markup :: HTML
|
|
27
|
-
Classifier: Typing :: Typed
|
|
28
|
-
Requires-Python: >=3.10
|
|
29
|
-
Requires-Dist: click>=8.1.0
|
|
30
|
-
Requires-Dist: httpx>=0.27.0
|
|
31
|
-
Requires-Dist: rich>=13.0.0
|
|
32
|
-
Provides-Extra: dev
|
|
33
|
-
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
34
|
-
Description-Content-Type: text/markdown
|
|
35
|
-
|
|
36
|
-
# Diffbot Python Library
|
|
37
|
-
|
|
38
|
-
Python client library for [Diffbot](https://www.diffbot.com) APIs.
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
## Installation
|
|
42
|
-
|
|
43
|
-
```bash
|
|
44
|
-
python3 -m pip install diffbot-python
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
Or, for local development:
|
|
48
|
-
|
|
49
|
-
```bash
|
|
50
|
-
pip install -e ".[dev]"
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
## Usage
|
|
54
|
-
|
|
55
|
-
### Authentication
|
|
56
|
-
|
|
57
|
-
The CLI and the library can share a single credential. The token always has to be
|
|
58
|
-
passed to the client explicitly, but `resolve_token()` gives you the same lookup the
|
|
59
|
-
CLI uses, in this order:
|
|
60
|
-
|
|
61
|
-
1. An explicit token passed to `resolve_token(token)`.
|
|
62
|
-
2. The `DIFFBOT_API_TOKEN` environment variable.
|
|
63
|
-
3. A `DIFFBOT_API_TOKEN=...` line in `~/.diffbot/credentials`.
|
|
64
|
-
|
|
65
|
-
Set it once and it works for both the CLI and your scripts. Either export it:
|
|
66
|
-
|
|
67
|
-
```bash
|
|
68
|
-
export DIFFBOT_API_TOKEN=<TOKEN>
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
…or write it to the shared credentials file (handy for keeping it out of your shell environment):
|
|
72
|
-
|
|
73
|
-
```bash
|
|
74
|
-
mkdir -p ~/.diffbot
|
|
75
|
-
printf 'DIFFBOT_API_TOKEN=%s\n' '<TOKEN>' > ~/.diffbot/credentials
|
|
76
|
-
chmod 600 ~/.diffbot/credentials
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
With either in place, resolve the token and pass it to the client:
|
|
80
|
-
|
|
81
|
-
```python
|
|
82
|
-
from diffbot import Diffbot, resolve_token
|
|
83
|
-
|
|
84
|
-
db = Diffbot(token=resolve_token()) # from env var or ~/.diffbot/credentials
|
|
85
|
-
data = db.extract("https://www.example.com")
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
### Extract structured content
|
|
89
|
-
```python
|
|
90
|
-
from diffbot import Diffbot
|
|
91
|
-
|
|
92
|
-
db = Diffbot(token="YOUR_TOKEN")
|
|
93
|
-
data = db.extract("https://www.example.com")
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
### Ask Diffbot LLM
|
|
97
|
-
```python
|
|
98
|
-
from diffbot import Diffbot
|
|
99
|
-
|
|
100
|
-
db = Diffbot(token="YOUR_TOKEN")
|
|
101
|
-
for chunk in db.ask([{"role": "user", "content": "What's the capital of France?"}]):
|
|
102
|
-
print(chunk, end="")
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
### Crawl a site for structured content
|
|
106
|
-
```python
|
|
107
|
-
from diffbot import Diffbot
|
|
108
|
-
|
|
109
|
-
db = Diffbot(token="YOUR_TOKEN")
|
|
110
|
-
for event in db.crawl("https://www.example.com", hops=1):
|
|
111
|
-
print(event)
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
### Query the Knowledge Graph
|
|
115
|
-
```python
|
|
116
|
-
from diffbot import Diffbot
|
|
117
|
-
|
|
118
|
-
db = Diffbot(token="YOUR_TOKEN")
|
|
119
|
-
results = db.dql('type:Organization name:"Diffbot"')
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
### Web Search
|
|
123
|
-
```python
|
|
124
|
-
from diffbot import Diffbot
|
|
125
|
-
|
|
126
|
-
db = Diffbot(token="YOUR_TOKEN")
|
|
127
|
-
results = db.web_search("diffbot knowledge graph")
|
|
128
|
-
for r in results["search_results"]:
|
|
129
|
-
print(r["score"], r["title"], r["pageUrl"])
|
|
130
|
-
print(r["content"])
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
### Entities (NLP)
|
|
134
|
-
```python
|
|
135
|
-
from diffbot import Diffbot
|
|
136
|
-
|
|
137
|
-
db = Diffbot(token="YOUR_TOKEN")
|
|
138
|
-
result = db.entities("Apple CEO Tim Cook announced record quarterly earnings.")
|
|
139
|
-
for entity in result["entities"]:
|
|
140
|
-
print(entity["name"], entity.get("type"), entity.get("id"))
|
|
141
|
-
print("sentiment:", result.get("sentiment"))
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
## Async Usage
|
|
145
|
-
|
|
146
|
-
### Extract structured content
|
|
147
|
-
```python
|
|
148
|
-
import asyncio
|
|
149
|
-
from diffbot import DiffbotAsync
|
|
150
|
-
|
|
151
|
-
async def main():
|
|
152
|
-
async with DiffbotAsync(token="YOUR_TOKEN") as db:
|
|
153
|
-
data = await db.extract("https://www.example.com")
|
|
154
|
-
print(data)
|
|
155
|
-
|
|
156
|
-
asyncio.run(main())
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
### Ask Diffbot LLM
|
|
160
|
-
```python
|
|
161
|
-
import asyncio
|
|
162
|
-
from diffbot import DiffbotAsync
|
|
163
|
-
|
|
164
|
-
async def main():
|
|
165
|
-
async with DiffbotAsync(token="YOUR_TOKEN") as db:
|
|
166
|
-
async for chunk in db.ask([{"role": "user", "content": "What's the capital of France?"}]):
|
|
167
|
-
print(chunk, end="")
|
|
168
|
-
|
|
169
|
-
asyncio.run(main())
|
|
170
|
-
```
|
|
171
|
-
|
|
172
|
-
### Crawl a site for structured content
|
|
173
|
-
```python
|
|
174
|
-
import asyncio
|
|
175
|
-
from diffbot import DiffbotAsync
|
|
176
|
-
|
|
177
|
-
async def main():
|
|
178
|
-
async with DiffbotAsync(token="YOUR_TOKEN") as db:
|
|
179
|
-
async for event in db.crawl("https://www.example.com", hops=1):
|
|
180
|
-
print(event)
|
|
181
|
-
|
|
182
|
-
asyncio.run(main())
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
### Query the Knowledge Graph
|
|
186
|
-
```python
|
|
187
|
-
import asyncio
|
|
188
|
-
from diffbot import DiffbotAsync
|
|
189
|
-
|
|
190
|
-
async def main():
|
|
191
|
-
async with DiffbotAsync(token="YOUR_TOKEN") as db:
|
|
192
|
-
results = await db.dql('type:Organization name:"Diffbot"')
|
|
193
|
-
print(results)
|
|
194
|
-
|
|
195
|
-
asyncio.run(main())
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
### Web Search
|
|
199
|
-
```python
|
|
200
|
-
import asyncio
|
|
201
|
-
from diffbot import DiffbotAsync
|
|
202
|
-
|
|
203
|
-
async def main():
|
|
204
|
-
async with DiffbotAsync(token="YOUR_TOKEN") as db:
|
|
205
|
-
results = await db.web_search("diffbot knowledge graph")
|
|
206
|
-
for r in results["search_results"]:
|
|
207
|
-
print(r["score"], r["title"], r["pageUrl"])
|
|
208
|
-
print(r["content"])
|
|
209
|
-
|
|
210
|
-
asyncio.run(main())
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
### Entities (NLP)
|
|
214
|
-
```python
|
|
215
|
-
import asyncio
|
|
216
|
-
from diffbot import DiffbotAsync
|
|
217
|
-
|
|
218
|
-
async def main():
|
|
219
|
-
async with DiffbotAsync(token="YOUR_TOKEN") as db:
|
|
220
|
-
result = await db.entities("Apple CEO Tim Cook announced record quarterly earnings.")
|
|
221
|
-
for entity in result["entities"]:
|
|
222
|
-
print(entity["name"], entity.get("type"), entity.get("id"))
|
|
223
|
-
print("sentiment:", result.get("sentiment"))
|
|
224
|
-
|
|
225
|
-
asyncio.run(main())
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
## CLI
|
|
229
|
-
|
|
230
|
-
This library also includes a CLI exposed as the `db` command.
|
|
231
|
-
|
|
232
|
-
To make `db` available from anywhere, install it as an isolated tool with [uv](https://docs.astral.sh/uv/):
|
|
233
|
-
|
|
234
|
-
```bash
|
|
235
|
-
uv tool install .
|
|
236
|
-
```
|
|
237
|
-
|
|
238
|
-
This drops a `db` executable into `~/.local/bin` (ensure it is on your `PATH`). Use `--force` to reinstall or upgrade after changes, or `--editable` to have source edits take effect immediately. Alternatively, a plain `pip install .` (or `pip install -e .`) also installs the `db` entry point into the active environment.
|
|
239
|
-
|
|
240
|
-
```bash
|
|
241
|
-
export DIFFBOT_API_TOKEN=your-token-here
|
|
242
|
-
|
|
243
|
-
db extract https://www.example.com
|
|
244
|
-
db ask "What's the capital of France?"
|
|
245
|
-
db crawl https://www.example.com --hops 1
|
|
246
|
-
db crawl-list-jobs
|
|
247
|
-
db crawl-delete-job crawl-1234567890
|
|
248
|
-
db web-search "diffbot knowledge graph"
|
|
249
|
-
db web-search "diffbot knowledge graph" -n 5 -f json
|
|
250
|
-
db entities "Apple CEO Tim Cook announced record quarterly earnings."
|
|
251
|
-
db entities "Apple CEO Tim Cook announced record quarterly earnings." -f dql
|
|
252
|
-
```
|
|
253
|
-
|
|
254
|
-
## Tests
|
|
255
|
-
|
|
256
|
-
Run the mock test suite:
|
|
257
|
-
```bash
|
|
258
|
-
python -m pytest
|
|
259
|
-
```
|
|
260
|
-
|
|
261
|
-
Run live integration tests against the real API (requires a valid token).
|
|
262
|
-
The token is resolved the same way as everywhere else — the `DIFFBOT_API_TOKEN`
|
|
263
|
-
environment variable or `~/.diffbot/credentials`:
|
|
264
|
-
```bash
|
|
265
|
-
DIFFBOT_API_TOKEN=your_token python -m pytest -m live
|
|
266
|
-
```
|
|
File without changes
|
|
File without changes
|
|
File without changes
|