Add LangSearch web search tool for small model knowledge augmentation
Browse files- Dockerfile +2 -1
- README.md +23 -0
- requirements.txt +2 -1
- search_tool.py +61 -0
Dockerfile
CHANGED
|
@@ -63,8 +63,9 @@ RUN python3 -m venv /opt/venv \
|
|
| 63 |
&& rm -rf /root/.cache/pip
|
| 64 |
|
| 65 |
COPY download_model.py /app/download_model.py
|
|
|
|
| 66 |
COPY start.sh /app/start.sh
|
| 67 |
-
RUN chmod +x /app/start.sh \
|
| 68 |
&& mkdir -p /data/models /data/hf-cache \
|
| 69 |
&& chown -R 65532:65532 /app || true
|
| 70 |
|
|
|
|
| 63 |
&& rm -rf /root/.cache/pip
|
| 64 |
|
| 65 |
COPY download_model.py /app/download_model.py
|
| 66 |
+
COPY search_tool.py /app/search_tool.py
|
| 67 |
COPY start.sh /app/start.sh
|
| 68 |
+
RUN chmod +x /app/start.sh /app/search_tool.py \
|
| 69 |
&& mkdir -p /data/models /data/hf-cache \
|
| 70 |
&& chown -R 65532:65532 /app || true
|
| 71 |
|
README.md
CHANGED
|
@@ -31,6 +31,7 @@ Optional tuning variables:
|
|
| 31 |
- `CACHE_TYPE_K`: KV cache type for keys, default `q8_0`
|
| 32 |
- `CACHE_TYPE_V`: KV cache type for values, default `q8_0`
|
| 33 |
- `REASONING`: reasoning mode, accepts `True`, `False`, or `auto` and maps to llama.cpp `--reasoning`
|
|
|
|
| 34 |
- `PARALLEL`: optional override for concurrent prompt decode slots
|
| 35 |
- `PORT`: listen port, default `7860`
|
| 36 |
|
|
@@ -50,6 +51,28 @@ The Docker image uses a multi-stage build on Ubuntu 24.04. The builder stage ins
|
|
| 50 |
|
| 51 |
Model downloads and HF cache live on the `/data` bucket so restarts do not redownload the model.
|
| 52 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
## curl examples
|
| 54 |
|
| 55 |
```bash
|
|
|
|
| 31 |
- `CACHE_TYPE_K`: KV cache type for keys, default `q8_0`
|
| 32 |
- `CACHE_TYPE_V`: KV cache type for values, default `q8_0`
|
| 33 |
- `REASONING`: reasoning mode, accepts `True`, `False`, or `auto` and maps to llama.cpp `--reasoning`
|
| 34 |
+
- `LANGSEARCH_API_KEY`: optional API key for LangSearch web search tool (free tier: 1 req/sec, 60/min, 1000/day)
|
| 35 |
- `PARALLEL`: optional override for concurrent prompt decode slots
|
| 36 |
- `PORT`: listen port, default `7860`
|
| 37 |
|
|
|
|
| 51 |
|
| 52 |
Model downloads and HF cache live on the `/data` bucket so restarts do not redownload the model.
|
| 53 |
|
| 54 |
+
## Web Search Tool
|
| 55 |
+
|
| 56 |
+
If `LANGSEARCH_API_KEY` is set, use the search tool to augment small model knowledge:
|
| 57 |
+
|
| 58 |
+
```bash
|
| 59 |
+
python3 /app/search_tool.py "query" [count] [summary] [freshness]
|
| 60 |
+
```
|
| 61 |
+
|
| 62 |
+
Examples:
|
| 63 |
+
```bash
|
| 64 |
+
python3 /app/search_tool.py "latest AI news" 5 true noLimit
|
| 65 |
+
python3 /app/search_tool.py "Python 3.13 release" 3 false oneWeek
|
| 66 |
+
```
|
| 67 |
+
|
| 68 |
+
Parameters:
|
| 69 |
+
- `query`: search string (required)
|
| 70 |
+
- `count`: max results 1-10 (default: 5)
|
| 71 |
+
- `summary`: include summaries (default: true)
|
| 72 |
+
- `freshness`: `oneDay`, `oneWeek`, `oneMonth`, `oneYear`, `noLimit` (default: `noLimit`)
|
| 73 |
+
|
| 74 |
+
Output is JSON with search results, URLs, snippets, and optional summaries.
|
| 75 |
+
|
| 76 |
## curl examples
|
| 77 |
|
| 78 |
```bash
|
requirements.txt
CHANGED
|
@@ -1 +1,2 @@
|
|
| 1 |
-
huggingface_hub>=0.24,<1
|
|
|
|
|
|
| 1 |
+
huggingface_hub>=0.24,<1
|
| 2 |
+
requests>=2.31,<3
|
search_tool.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
|
| 3 |
+
import os
|
| 4 |
+
import sys
|
| 5 |
+
import json
|
| 6 |
+
import requests
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def search(query: str, count: int = 5, summary: bool = True, freshness: str = "noLimit") -> dict:
|
| 10 |
+
api_key = os.environ.get("LANGSEARCH_API_KEY", "").strip()
|
| 11 |
+
if not api_key:
|
| 12 |
+
raise SystemExit("LANGSEARCH_API_KEY environment variable not set")
|
| 13 |
+
|
| 14 |
+
url = "https://api.langsearch.com/v1/web-search"
|
| 15 |
+
headers = {
|
| 16 |
+
"Authorization": f"Bearer {api_key}",
|
| 17 |
+
"Content-Type": "application/json"
|
| 18 |
+
}
|
| 19 |
+
payload = {
|
| 20 |
+
"query": query,
|
| 21 |
+
"freshness": freshness,
|
| 22 |
+
"summary": summary,
|
| 23 |
+
"count": min(count, 10)
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
try:
|
| 27 |
+
response = requests.post(url, headers=headers, json=payload, timeout=30)
|
| 28 |
+
response.raise_for_status()
|
| 29 |
+
data = response.json()
|
| 30 |
+
|
| 31 |
+
if data.get("code") == 200:
|
| 32 |
+
results = []
|
| 33 |
+
web_pages = data.get("data", {}).get("webPages", {}).get("value", [])
|
| 34 |
+
for page in web_pages:
|
| 35 |
+
results.append({
|
| 36 |
+
"name": page.get("name", ""),
|
| 37 |
+
"url": page.get("url", ""),
|
| 38 |
+
"snippet": page.get("snippet", ""),
|
| 39 |
+
"summary": page.get("summary", "") if summary else ""
|
| 40 |
+
})
|
| 41 |
+
return {"success": True, "query": query, "results": results}
|
| 42 |
+
else:
|
| 43 |
+
return {"success": False, "error": data.get("msg", "Unknown error")}
|
| 44 |
+
|
| 45 |
+
except requests.RequestException as e:
|
| 46 |
+
return {"success": False, "error": str(e)}
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
if __name__ == "__main__":
|
| 50 |
+
if len(sys.argv) < 2:
|
| 51 |
+
print("Usage: search_tool.py '<query>' [count] [summary] [freshness]")
|
| 52 |
+
print("Example: search_tool.py 'Apple M4 chip' 5 true noLimit")
|
| 53 |
+
sys.exit(1)
|
| 54 |
+
|
| 55 |
+
query = sys.argv[1]
|
| 56 |
+
count = int(sys.argv[2]) if len(sys.argv) > 2 else 5
|
| 57 |
+
summary = sys.argv[3].lower() == "true" if len(sys.argv) > 3 else True
|
| 58 |
+
freshness = sys.argv[4] if len(sys.argv) > 4 else "noLimit"
|
| 59 |
+
|
| 60 |
+
result = search(query, count, summary, freshness)
|
| 61 |
+
print(json.dumps(result, indent=2))
|