answerline-langchain 0.1.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.
- answerline_langchain/__init__.py +84 -0
- answerline_langchain/input.py +40 -0
- answerline_langchain/schemas.py +169 -0
- answerline_langchain-0.1.0.dist-info/METADATA +54 -0
- answerline_langchain-0.1.0.dist-info/RECORD +8 -0
- answerline_langchain-0.1.0.dist-info/WHEEL +5 -0
- answerline_langchain-0.1.0.dist-info/licenses/LICENSE +21 -0
- answerline_langchain-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""LangChain tools for the answer and search monitoring API, built on the `answerline` client."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import dataclasses
|
|
6
|
+
import json
|
|
7
|
+
from typing import Any, Callable, Dict, Iterable, List, Literal, Optional, Sequence, Union
|
|
8
|
+
|
|
9
|
+
from answerline import ApiError, Client
|
|
10
|
+
from langchain_core.tools import BaseTool, StructuredTool, ToolException
|
|
11
|
+
from pydantic import BaseModel, create_model
|
|
12
|
+
|
|
13
|
+
from .input import Input, Operation
|
|
14
|
+
from .schemas import CREATE_ASYNC_TASK, GET_CREDITS, GET_TASK_STATUS, MONITOR_OPERATIONS
|
|
15
|
+
|
|
16
|
+
__all__ = ["create_tools"]
|
|
17
|
+
|
|
18
|
+
Call = Callable[[Dict[str, Any]], Any]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def create_tools(client: Client, engines: Optional[Iterable[str]] = None) -> List[BaseTool]:
|
|
22
|
+
"""A sync monitor tool per served engine, plus async task submission, task status and credits.
|
|
23
|
+
|
|
24
|
+
`engines` lists the task types the deployment serves (its `served_engines`); every priced engine by default.
|
|
25
|
+
"""
|
|
26
|
+
served = set(op.task_type for op in MONITOR_OPERATIONS) if engines is None else set(engines)
|
|
27
|
+
monitors = [op for op in MONITOR_OPERATIONS if op.task_type in served]
|
|
28
|
+
if not monitors or len(monitors) < len(served):
|
|
29
|
+
raise ValueError(f"engines must be priced engines, at least one: {sorted(served)}")
|
|
30
|
+
|
|
31
|
+
def monitor(route: str) -> Call:
|
|
32
|
+
return lambda body: client.monitor(route, **body)
|
|
33
|
+
|
|
34
|
+
def create_task(body: Dict[str, Any]) -> Any:
|
|
35
|
+
webhook = body.get("webhook")
|
|
36
|
+
webhook_url = webhook.get("url") if isinstance(webhook, dict) else None
|
|
37
|
+
return client.create_task(body["taskType"], body["payload"], body.get("priority"), body.get("idempotencyKey"), webhook_url)
|
|
38
|
+
|
|
39
|
+
return [
|
|
40
|
+
*(_tool(op, monitor(op.route)) for op in monitors),
|
|
41
|
+
_tool(dataclasses.replace(CREATE_ASYNC_TASK, args_schema=_task_schema(monitors)), create_task),
|
|
42
|
+
_tool(GET_TASK_STATUS, lambda body: client.get_task(body["taskId"])),
|
|
43
|
+
_tool(GET_CREDITS, lambda body: client.credits()),
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _task_schema(monitors: Sequence[Operation]) -> type[Input]:
|
|
48
|
+
"""The async task input narrowed to the task types and payloads of `monitors`."""
|
|
49
|
+
schema = CREATE_ASYNC_TASK.args_schema
|
|
50
|
+
fields = schema.model_fields
|
|
51
|
+
return create_model(
|
|
52
|
+
schema.__name__,
|
|
53
|
+
__base__=schema,
|
|
54
|
+
taskType=(Literal[tuple(op.task_type for op in monitors)], fields["taskType"]),
|
|
55
|
+
payload=(Union[tuple(op.args_schema for op in monitors)], fields["payload"]),
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _tool(operation: Operation, call: Call) -> BaseTool:
|
|
60
|
+
def run(**arguments: Any) -> Any:
|
|
61
|
+
try:
|
|
62
|
+
return call(_body(arguments))
|
|
63
|
+
# Handled as the tool's result, so the model sees why the call failed and can correct or retry it.
|
|
64
|
+
except ApiError as err:
|
|
65
|
+
raise ToolException(f"{err}: {json.dumps(err.body)}") from err
|
|
66
|
+
except OSError as err:
|
|
67
|
+
raise ToolException(f"request failed: {type(err).__name__}: {err}") from err
|
|
68
|
+
|
|
69
|
+
return StructuredTool.from_function(
|
|
70
|
+
func=run,
|
|
71
|
+
name=operation.name,
|
|
72
|
+
description=operation.description,
|
|
73
|
+
args_schema=operation.args_schema,
|
|
74
|
+
handle_tool_error=True,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _body(arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
79
|
+
"""Validated arguments as JSON. LangChain passes nested objects as models and omitted fields as None."""
|
|
80
|
+
return {
|
|
81
|
+
name: value.model_dump(exclude_none=True) if isinstance(value, BaseModel) else value
|
|
82
|
+
for name, value in arguments.items()
|
|
83
|
+
if value is not None
|
|
84
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Base types of the generated tool inputs in `schemas`."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Any, ClassVar, Dict, Optional, Tuple, Type
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel, ConfigDict, ValidationInfo, field_validator, model_validator
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Input(BaseModel):
|
|
10
|
+
"""A tool input. Unknown fields are refused, as the API refuses them."""
|
|
11
|
+
|
|
12
|
+
model_config = ConfigDict(extra="forbid")
|
|
13
|
+
# The schema's `anyOf`: every field of at least one of these sets must be given.
|
|
14
|
+
required_any_of: ClassVar[Tuple[Tuple[str, ...], ...]] = ()
|
|
15
|
+
# An async task's payload model by task type.
|
|
16
|
+
payload_types: ClassVar[Dict[str, Type["Input"]]] = {}
|
|
17
|
+
|
|
18
|
+
@model_validator(mode="after")
|
|
19
|
+
def _check_required_any_of(self) -> "Input":
|
|
20
|
+
sets = self.required_any_of
|
|
21
|
+
if sets and not any(all(getattr(self, field) is not None for field in fields) for fields in sets):
|
|
22
|
+
raise ValueError("provide " + " or ".join(" and ".join(fields) for fields in sets))
|
|
23
|
+
return self
|
|
24
|
+
|
|
25
|
+
@field_validator("payload", mode="before", check_fields=False)
|
|
26
|
+
@classmethod
|
|
27
|
+
def _validate_payload_as_its_task_type(cls, payload: Any, info: ValidationInfo) -> Any:
|
|
28
|
+
# An invalid task type is reported on its own field; the payload is then left to its declared type.
|
|
29
|
+
payload_type = cls.payload_types.get(info.data.get("taskType"))
|
|
30
|
+
return payload_type.model_validate(payload) if payload_type else payload
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass(frozen=True)
|
|
34
|
+
class Operation:
|
|
35
|
+
name: str
|
|
36
|
+
description: str
|
|
37
|
+
args_schema: Type[Input]
|
|
38
|
+
# Path under /v1/monitor/ and task type of a monitor operation.
|
|
39
|
+
route: Optional[str] = None
|
|
40
|
+
task_type: Optional[str] = None
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# Generated by integrations/codegen from openapi/openapi.json and config/pricing.toml. Do not edit.
|
|
2
|
+
from typing import Literal, Optional, Union
|
|
3
|
+
|
|
4
|
+
from pydantic import Field
|
|
5
|
+
|
|
6
|
+
from .input import Input, Operation
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class MonitorChatgptInputInclude(Input):
|
|
10
|
+
html: Optional[bool] = Field(None, description="Include a URL to the full HTML of the response")
|
|
11
|
+
markdown: Optional[bool] = Field(None, description="Include markdown-formatted response in the result")
|
|
12
|
+
rawResponse: Optional[bool] = Field(None, description="Include ChatGPT's raw response payload")
|
|
13
|
+
searchQueries: Optional[bool] = Field(None, description="Include the query fan-out ChatGPT used to generate the response")
|
|
14
|
+
ads: Optional[bool] = Field(None, description="Include ads displayed in ChatGPT response")
|
|
15
|
+
shopping: Optional[bool] = Field(None, description="Include shopping cards and inline products with pricing and offers. Adds +2 credits to the base cost (shared with rawResponse, searchQueries, and ads — enabling any one or any combination adds the same +2).")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class MonitorChatgptInput(Input):
|
|
19
|
+
prompt: str = Field(description="The prompt to send to ChatGPT", min_length=1, max_length=10000)
|
|
20
|
+
country: str = Field(description="Country/region code for localized response")
|
|
21
|
+
include: Optional[MonitorChatgptInputInclude] = Field(None, description="Optional flags for including additional response formats")
|
|
22
|
+
legacy: Optional[bool] = Field(None, description="Serve ChatGPT's legacy desktop interface instead of the default mobile-web one. The legacy interface streams the answer as an event stream, so `result.model`, `result.searchQueries` and `result.mapSearchQueries` are populated. Best-effort and temporary: OpenAI controls which interface it serves and can retire the legacy one at any time, so a request can still come back in the mobile-web shape. Handle both. Defaults to false.")
|
|
23
|
+
disableWebSearch: Optional[bool] = Field(None, description="Do not force ChatGPT's web search. answerline returns the answer ChatGPT gives on its own, which uses web search only when ChatGPT decides to, so `result.sources`, `result.citationPills` and `result.searchQueries` are often empty. Defaults to false: web search is forced on every request.")
|
|
24
|
+
state: Optional[str] = Field(None, description="State code for sub-country geo-targeting (e.g., \"CA\"). Only valid with country \"US\".", min_length=2, max_length=2, pattern="^[A-Z]{2}$")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class MonitorGeminiInputInclude(Input):
|
|
28
|
+
markdown: Optional[bool] = Field(None, description="Include markdown-formatted response in the result")
|
|
29
|
+
html: Optional[bool] = Field(None, description="Include HTML-formatted response in the result")
|
|
30
|
+
rawResponse: Optional[bool] = Field(None, description="Include raw streaming response events")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class MonitorGeminiInput(Input):
|
|
34
|
+
prompt: str = Field(description="The prompt to send to Gemini", min_length=1, max_length=10000)
|
|
35
|
+
country: str = Field(description="Country/region code for localized response")
|
|
36
|
+
include: Optional[MonitorGeminiInputInclude] = Field(None, description="Optional flags for including additional response formats")
|
|
37
|
+
state: Optional[str] = Field(None, description="State code for sub-country geo-targeting (e.g., \"CA\"). Only valid with country \"US\".", min_length=2, max_length=2, pattern="^[A-Z]{2}$")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class MonitorPerplexityInputInclude(Input):
|
|
41
|
+
html: Optional[bool] = Field(None, description="Include a URL to the full HTML of the response")
|
|
42
|
+
markdown: Optional[bool] = Field(None, description="Include markdown-formatted response in the result")
|
|
43
|
+
rawResponse: Optional[bool] = Field(None, description="Include raw streaming response events")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class MonitorPerplexityInput(Input):
|
|
47
|
+
prompt: str = Field(description="The prompt to send to Perplexity", min_length=1, max_length=10000)
|
|
48
|
+
country: str = Field(description="Country/region code for localized response")
|
|
49
|
+
include: Optional[MonitorPerplexityInputInclude] = Field(None, description="Optional flags for including additional response formats")
|
|
50
|
+
state: Optional[str] = Field(None, description="State code for sub-country geo-targeting (e.g., \"CA\"). Only valid with country \"US\".", min_length=2, max_length=2, pattern="^[A-Z]{2}$")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class MonitorCopilotInputInclude(Input):
|
|
54
|
+
html: Optional[bool] = Field(None, description="Include a URL to the full HTML of the response")
|
|
55
|
+
markdown: Optional[bool] = Field(None, description="Include markdown-formatted response in the result")
|
|
56
|
+
rawResponse: Optional[bool] = Field(None, description="Include raw streaming response events")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class MonitorCopilotInput(Input):
|
|
60
|
+
prompt: str = Field(description="The prompt to send to Copilot", min_length=1, max_length=10000)
|
|
61
|
+
country: str = Field(description="Country/region code for localized response")
|
|
62
|
+
include: Optional[MonitorCopilotInputInclude] = Field(None, description="Optional flags for including additional response formats")
|
|
63
|
+
state: Optional[str] = Field(None, description="State code for sub-country geo-targeting (e.g., \"CA\"). Only valid with country \"US\".", min_length=2, max_length=2, pattern="^[A-Z]{2}$")
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class MonitorAiModeInputInclude(Input):
|
|
67
|
+
html: Optional[bool] = Field(None, description="Include a URL to the full HTML of the response")
|
|
68
|
+
markdown: Optional[bool] = Field(None, description="Include markdown-formatted response in the result")
|
|
69
|
+
expandProducts: Optional[bool] = Field(None, description="Fetch merchant offers for the product clusters in the response and return them as 'result.productResults'. Clusters are deduplicated across shopping cards and inline products, and expansion covers at most 6 per scrape. Charges +1 credit per cluster returned, to a maximum of +6. Off by default.")
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class MonitorAiModeInput(Input):
|
|
73
|
+
prompt: str = Field(description="The prompt to send to AI Mode", min_length=1, max_length=10000)
|
|
74
|
+
country: Optional[str] = Field(None, description="Country/region code for localized response")
|
|
75
|
+
gl: Optional[str] = Field(None, description="Alternative to `country`, using Google's own parameter name for the result geography. Same ISO 3166-1 alpha-2 codes, accepted in either case (`us` or `US`), and must be a country the endpoint supports — see `GET /v1/countries`. Provide `country` or `gl`; sending both with different values is a `400`.")
|
|
76
|
+
hl: Optional[str] = Field(None, description="Interface language, as Google's own `hl` parameter (e.g. `en`, `de`, `pt-BR`). Free-form and case-insensitive — Google owns the list of codes. Overrides the language answerline derives from the country, so the interface language and the result geography can differ.")
|
|
77
|
+
location: Optional[str] = Field(None, description="Google canonical location name for geo-targeted results, in comma-separated format: 'City,Region,Country' (see https://developers.google.com/google-ads/api/reference/data/geotargets for all ~100,000 supported locations). Use alongside 'country' for city-level precision. Mutually exclusive with 'uule' — provide one or the other, not both.")
|
|
78
|
+
uule: Optional[str] = Field(None, description="Pre-encoded Google UULE string for precise geo-targeting. Use this when you have a pre-built UULE value instead of a location name. Mutually exclusive with 'location' — provide one or the other, not both.")
|
|
79
|
+
device: Optional[Literal["desktop", "mobile"]] = Field(None, description="Device type for search results")
|
|
80
|
+
include: Optional[MonitorAiModeInputInclude] = Field(None, description="Optional flags for including additional response formats")
|
|
81
|
+
required_any_of = (("country",), ("gl",))
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class MonitorGoogleInputIncludeAioverview(Input):
|
|
85
|
+
markdown: Optional[bool] = Field(None, description="Include AI Overview formatted as markdown")
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class MonitorGoogleInputInclude(Input):
|
|
89
|
+
html: Optional[bool] = Field(None, description="Include raw HTML response from Google search")
|
|
90
|
+
aioverview: Optional[MonitorGoogleInputIncludeAioverview] = Field(None, description="Include Google AI Overview in the response")
|
|
91
|
+
paaAioverview: Optional[bool] = Field(None, description="Hydrate AI-Overview-type People Also Ask items with markdown content and sources. No additional credit cost beyond the +2 AI Overview add-on; each hydrated item adds one scraper fetch (typically 1-2 per page), so responses take longer.")
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class MonitorGoogleInput(Input):
|
|
95
|
+
query: Optional[str] = Field(None, description="The search query to execute on Google. Required unless you supply 'url' instead — the two are mutually exclusive.", min_length=1, max_length=10000)
|
|
96
|
+
url: Optional[str] = Field(None, description="A complete Google web search URL to run instead of building the request from 'query' and the targeting fields. Must be an absolute http(s) URL on a Google web search host (google.com, google.co.uk, www.google.de, ...), with the path '/search' and a non-empty 'q'. Mutually exclusive with 'query', 'location', 'uule' and 'pages' — the URL owns those values. answerline applies a fixed set of query parameters — 'q', 'gl', 'hl', 'uule', 'num' (read as result depth), 'start', 'tbs' and 'safe' — and drops anything else. URLs carrying 'tbm' are rejected, because they target a Google vertical with its own endpoint and pricing (use POST /v1/monitor/google/news for tbm=nws).")
|
|
97
|
+
country: Optional[str] = Field(None, description="ISO 3166-1 alpha-2 country code for localized search results. Required in the standard request shape. When you supply 'url' instead, country is derived from the URL's 'gl' — pass it explicitly to override that, or when the URL has no 'gl'.")
|
|
98
|
+
gl: Optional[str] = Field(None, description="Alternative to `country`, using Google's own parameter name for the result geography. Same ISO 3166-1 alpha-2 codes, accepted in either case (`us` or `US`), and must be a country the endpoint supports — see `GET /v1/countries`. Provide `country` or `gl`; sending both with different values is a `400`.")
|
|
99
|
+
hl: Optional[str] = Field(None, description="Interface language, as Google's own `hl` parameter (e.g. `en`, `de`, `pt-BR`). Free-form and case-insensitive — Google owns the list of codes. Overrides the language answerline derives from the country, so the interface language and the result geography can differ.")
|
|
100
|
+
location: Optional[str] = Field(None, description="Google canonical location name for geo-targeted results, in comma-separated format: 'City,Region,Country' (see https://developers.google.com/google-ads/api/reference/data/geotargets for all ~100,000 supported locations). Use alongside 'country' for city-level precision. Mutually exclusive with 'uule' — provide one or the other, not both. Cannot be combined with 'url'.")
|
|
101
|
+
uule: Optional[str] = Field(None, description="Pre-encoded Google UULE string for precise geo-targeting. Use this when you have a pre-built UULE value instead of a location name. Mutually exclusive with 'location' — provide one or the other, not both. Cannot be combined with 'url'; put 'uule' in the URL's query string instead.")
|
|
102
|
+
device: Optional[Literal["desktop", "mobile", "ios", "android"]] = Field(None, description="Device the search is run from. 'desktop' returns the desktop SERP; 'ios' (Safari on iPhone), 'android' (Chrome on Android) and 'mobile' (alias for 'android') all return the mobile SERP.")
|
|
103
|
+
pages: Optional[int] = Field(None, description="Number of search results pages to scrape (1-10). Cannot be combined with 'url' — in that shape, depth comes from the URL's 'num' (rounded up to whole pages of 10, capped at 10 pages).", ge=1, le=10)
|
|
104
|
+
include: Optional[MonitorGoogleInputInclude] = Field(None, description="Optional flags for including additional response data")
|
|
105
|
+
required_any_of = (("query", "country"), ("query", "gl"), ("url",))
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class MonitorGoogleNewsInputInclude(Input):
|
|
109
|
+
html: Optional[bool] = Field(None, description="Include raw HTML response from Google News")
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class MonitorGoogleNewsInput(Input):
|
|
113
|
+
query: str = Field(description="The search query to execute on Google News", min_length=1, max_length=10000)
|
|
114
|
+
country: Optional[str] = Field(None, description="ISO 3166-1 alpha-2 country code for localized news results")
|
|
115
|
+
gl: Optional[str] = Field(None, description="Alternative to `country`, using Google's own parameter name for the result geography. Same ISO 3166-1 alpha-2 codes, accepted in either case (`us` or `US`), and must be a country the endpoint supports — see `GET /v1/countries`. Provide `country` or `gl`; sending both with different values is a `400`.")
|
|
116
|
+
hl: Optional[str] = Field(None, description="Interface language, as Google's own `hl` parameter (e.g. `en`, `de`, `pt-BR`). Free-form and case-insensitive — Google owns the list of codes. Overrides the language answerline derives from the country, so the interface language and the result geography can differ.")
|
|
117
|
+
device: Optional[Literal["desktop", "mobile", "ios", "android"]] = Field(None, description="Device the search is run from. 'desktop' returns the desktop news SERP; 'ios' (Safari on iPhone), 'android' (Chrome on Android) and 'mobile' (alias for 'android') all return the mobile news SERP.")
|
|
118
|
+
pages: Optional[int] = Field(None, description="Number of news results pages to scrape (1-10)", ge=1, le=10)
|
|
119
|
+
include: Optional[MonitorGoogleNewsInputInclude] = Field(None, description="Optional flags for including additional response data")
|
|
120
|
+
required_any_of = (("country",), ("gl",))
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class MonitorGrokInputInclude(Input):
|
|
124
|
+
markdown: Optional[bool] = Field(None, description="Include markdown-formatted response in the result")
|
|
125
|
+
html: Optional[bool] = Field(None, description="Include HTML-formatted response in the result")
|
|
126
|
+
rawResponse: Optional[bool] = Field(None, description="Include raw streaming response events")
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class MonitorGrokInput(Input):
|
|
130
|
+
prompt: str = Field(description="The prompt to send to Grok", min_length=1, max_length=10000)
|
|
131
|
+
country: str = Field(description="Country/region code for localized response")
|
|
132
|
+
include: Optional[MonitorGrokInputInclude] = Field(None, description="Optional flags for including additional response formats")
|
|
133
|
+
state: Optional[str] = Field(None, description="State code for sub-country geo-targeting (e.g., \"CA\"). Only valid with country \"US\".", min_length=2, max_length=2, pattern="^[A-Z]{2}$")
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class CreateAsyncTaskInputWebhook(Input):
|
|
137
|
+
url: str = Field(description="URL to receive the webhook POST request when the task completes.")
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class CreateAsyncTaskInput(Input):
|
|
141
|
+
taskType: Literal["CHATGPT", "GEMINI", "PERPLEXITY", "COPILOT", "AIMODE", "GOOGLE", "GOOGLE_NEWS", "GROK"] = Field(description="The AI provider to use for this task.")
|
|
142
|
+
payload: Union[MonitorChatgptInput, MonitorGeminiInput, MonitorPerplexityInput, MonitorCopilotInput, MonitorAiModeInput, MonitorGoogleInput, MonitorGoogleNewsInput, MonitorGrokInput] = Field(description="Provider-specific request payload. Must include at least `prompt` (or `query` for Google Search).")
|
|
143
|
+
priority: Optional[int] = Field(None, description="Task priority level (1-10). Higher numbers are processed first. Defaults to 1.", ge=1, le=10)
|
|
144
|
+
idempotencyKey: Optional[str] = Field(None, description="Unique string to prevent duplicate task creation. Must be unique across your account.")
|
|
145
|
+
webhook: Optional[CreateAsyncTaskInputWebhook] = Field(None, description="Webhook configuration for task completion notification.")
|
|
146
|
+
payload_types = {"CHATGPT": MonitorChatgptInput, "GEMINI": MonitorGeminiInput, "PERPLEXITY": MonitorPerplexityInput, "COPILOT": MonitorCopilotInput, "AIMODE": MonitorAiModeInput, "GOOGLE": MonitorGoogleInput, "GOOGLE_NEWS": MonitorGoogleNewsInput, "GROK": MonitorGrokInput}
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class GetTaskStatusInput(Input):
|
|
150
|
+
taskId: str = Field(description="The ID of the task to fetch.")
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
class GetCreditsInput(Input):
|
|
154
|
+
pass
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
MONITOR_OPERATIONS = (
|
|
158
|
+
Operation("monitor_chatgpt", "Extract structured data from ChatGPT — answer text, cited sources, shopping cards, brand entities, and ads — for any prompt across supported regions", MonitorChatgptInput, route="chatgpt", task_type="CHATGPT"),
|
|
159
|
+
Operation("monitor_gemini", "Extract structured data from Google Gemini — generated answer text and cited sources for any prompt, with Markdown and HTML export formats", MonitorGeminiInput, route="gemini", task_type="GEMINI"),
|
|
160
|
+
Operation("monitor_perplexity", "Extract structured data from Perplexity AI about your brand, products, or any topic across various regions", MonitorPerplexityInput, route="perplexity", task_type="PERPLEXITY"),
|
|
161
|
+
Operation("monitor_copilot", "Extract structured data from Microsoft Copilot about your brand, products, or any topic across various regions", MonitorCopilotInput, route="copilot", task_type="COPILOT"),
|
|
162
|
+
Operation("monitor_ai_mode", "Extract structured data from AI Mode about your brand, products, or any topic across various regions", MonitorAiModeInput, route="aimode", task_type="AIMODE"),
|
|
163
|
+
Operation("monitor_google", "Extract structured data from Google Search including organic results, People Also Ask, related searches, and optional AI Overview", MonitorGoogleInput, route="google", task_type="GOOGLE"),
|
|
164
|
+
Operation("monitor_google_news", "Extract structured news articles from Google News including titles, links, snippets, sources, dates, and thumbnails", MonitorGoogleNewsInput, route="google/news", task_type="GOOGLE_NEWS"),
|
|
165
|
+
Operation("monitor_grok", "Extract structured data from Grok — answer text and cited sources, including X/Twitter references — for any prompt across supported regions", MonitorGrokInput, route="grok", task_type="GROK"),
|
|
166
|
+
)
|
|
167
|
+
CREATE_ASYNC_TASK = Operation("create_async_task", "Submit an asynchronous task for background processing. Returns a task ID that you can use to poll for results or receive via webhook.", CreateAsyncTaskInput)
|
|
168
|
+
GET_TASK_STATUS = Operation("get_task_status", "Poll the status and result of an asynchronous task by ID. Returns the task state and, once complete, the full structured result payload.", GetTaskStatusInput)
|
|
169
|
+
GET_CREDITS = Operation("get_credits", "Read your organization's current credit balance and billing cycle. Lets you observe your balance programmatically — including from async-only workloads — without making a sync /v1/monitor/* request just to inspect the X-Credits-Remaining header. The authoritative check is enforced when each task is charged.", GetCreditsInput)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: answerline-langchain
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: LangChain tools for the AnswerLine answer and search monitoring API.
|
|
5
|
+
Author-email: AnswerLine <support@answerline.dev>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://answerline.dev
|
|
8
|
+
Project-URL: Repository, https://github.com/hsdxpro/answerline
|
|
9
|
+
Keywords: answerline,ai,llm,langchain,search-api,monitoring
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: answerline<0.2,>=0.1.0
|
|
19
|
+
Requires-Dist: langchain-core<2,>=1.6.3
|
|
20
|
+
Requires-Dist: pydantic<3,>=2.13.5
|
|
21
|
+
Dynamic: license-file
|
|
22
|
+
|
|
23
|
+
# answerline-langchain
|
|
24
|
+
|
|
25
|
+
LangChain tools for the answer and search monitoring API, built on the Python SDK (`answerline`).
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pip install answerline-langchain
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
`create_tools(client, engines=...)` returns one sync monitor tool per engine (`monitor_chatgpt`, `monitor_google_news`, ...), plus `create_async_task`, `get_task_status` and `get_credits`. `engines` lists the task types the deployment serves (its `served_engines`; the API refuses priced engines outside it) and defaults to every priced engine. An async task's `payload` is validated as the monitor request of its `taskType`. API errors, timeouts and connection errors come back to the model as the tool result.
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
from answerline import Client
|
|
35
|
+
from answerline_langchain import create_tools
|
|
36
|
+
|
|
37
|
+
tools = create_tools(Client("sk_..."), engines=["CHATGPT"])
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Tool input models in `src/answerline_langchain/schemas.py` are generated from `openapi/openapi.json` and the priced engines in `config/pricing.toml` by `integrations/codegen` (`npm run generate` there); do not edit them by hand.
|
|
41
|
+
|
|
42
|
+
## Develop
|
|
43
|
+
|
|
44
|
+
Until `answerline` is published, install the SDK from the repository:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
python -m venv .venv && .venv/bin/pip install -e ../../sdk/python -e .
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
.venv/bin/python -m unittest discover -s tests
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Tests run offline against a local stub server.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
answerline_langchain/__init__.py,sha256=__94UwSS_Ysuj2hEeVLRGKi7dXQZ5XkIkk9xnSOtpeM,3495
|
|
2
|
+
answerline_langchain/input.py,sha256=OGbVe6yFPFyNiJ38tEMVX5d_1rbbrLvEW3HeaaqQ0UM,1684
|
|
3
|
+
answerline_langchain/schemas.py,sha256=BRlfoiV8LNb9h3hIgwHEiEHcCqJRhiLraiCJQ4mmun4,18458
|
|
4
|
+
answerline_langchain-0.1.0.dist-info/licenses/LICENSE,sha256=WEk3fXEcZoGoQtMd3Cu5aLyGrVtKajmtDHZaUlpSvEE,1067
|
|
5
|
+
answerline_langchain-0.1.0.dist-info/METADATA,sha256=AE4v7IWbNgSYSWYSQQTdjU6JUHjhTrBMo5smlOPpAic,2249
|
|
6
|
+
answerline_langchain-0.1.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
7
|
+
answerline_langchain-0.1.0.dist-info/top_level.txt,sha256=2QYzIAsb49Cn_Unl1AFJC6qX3SjhmuIk4RAAFBD180A,21
|
|
8
|
+
answerline_langchain-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 AnswerLine
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
answerline_langchain
|