From ab0966b2eceb0454c27ed0d3d290d68b471f17e9 Mon Sep 17 00:00:00 2001 From: Ilya Bogin Date: Sun, 21 Jun 2026 20:30:00 +0300 Subject: [PATCH] Add Keenable integration (web search + page fetch, keyless by default) --- integrations/keenable.md | 126 +++++++++++++++++++++++++++++++++++++++ logos/keenable.png | Bin 0 -> 12030 bytes 2 files changed, 126 insertions(+) create mode 100644 integrations/keenable.md create mode 100644 logos/keenable.png diff --git a/integrations/keenable.md b/integrations/keenable.md new file mode 100644 index 00000000..5597fa3f --- /dev/null +++ b/integrations/keenable.md @@ -0,0 +1,126 @@ +--- +layout: integration +name: Keenable +description: Web search and page fetch built for AI agents, keyless by default +authors: + - name: Keenable + socials: + github: keenableai +pypi: https://pypi.org/project/keenable-haystack +repo: https://github.com/keenableai/keenable-haystack +report_issue: https://github.com/keenableai/keenable-haystack/issues +type: Data Ingestion +logo: /logos/keenable.png +version: Haystack 2.0 +toc: true +--- + +### Table of Contents + +- [Overview](#overview) +- [Installation](#installation) +- [Usage](#usage) + - [KeenableWebSearch](#keenablewebsearch) + - [KeenableFetcher](#keenablefetcher) +- [License](#license) + +## Overview + +[Keenable](https://keenable.ai) is a web search and page-fetch API built for AI +agents. This integration provides two components: + +- `KeenableWebSearch`: searches the web and returns results as Haystack + `Document` objects plus their URLs (`links`), the same output shape as the + built-in `SerperDevWebSearch` / `SearchApiWebSearch`, so it drops into existing + web-search pipelines. +- `KeenableFetcher`: fetches one or more URLs and returns their main content as + Haystack `Document` objects (extracted to clean markdown server-side, so you + don't need a separate `LinkContentFetcher` + `HTMLToDocument` step). + +**Keyless by default.** With no API key, the components use Keenable's keyless +public endpoints — no signup required to try it. Set a `KEENABLE_API_KEY` to use +the authenticated endpoints (required for `mode="realtime"` and for higher rate +limits). + +## Installation + +```bash +pip install keenable-haystack +``` + +## Usage + +### KeenableWebSearch + +#### Basic Example + +```python +from haystack_integrations.components.websearch.keenable import KeenableWebSearch + +# No API key -> keyless public endpoint. Set KEENABLE_API_KEY to lift limits. +web_search = KeenableWebSearch(top_k=5) + +result = web_search.run(query="latest developments in AI agents") +for doc in result["documents"]: + print(doc.content) +print(result["links"]) +``` + +You can pass an API key explicitly instead of using the environment variable: + +```python +from haystack.utils import Secret +from haystack_integrations.components.websearch.keenable import KeenableWebSearch + +web_search = KeenableWebSearch(api_key=Secret.from_token("your-api-key"), top_k=5) +``` + +#### Parameters + +- **`api_key`**: Keenable API key. Defaults to the `KEENABLE_API_KEY` environment + variable; when absent the keyless public endpoint is used. +- **`top_k`**: Keep at most this many results (applied client-side). `None` keeps all. +- **`mode`**: Default search mode, `"pro"` (deeper) or `"realtime"` (low latency, + key required). Overridable per `run`. +- **`site`**: Default single-domain restriction (e.g. `"github.com"`). Overridable per `run`. + +`run` also accepts optional per-query filters: `site`, `published_after`, +`published_before`, `acquired_after`, `acquired_before`, `mode`. + +#### In a Pipeline + +`KeenableWebSearch` is a drop-in for any web-search component — connect its +`documents` output downstream: + +```python +from haystack import Pipeline +from haystack.components.builders import PromptBuilder +from haystack_integrations.components.websearch.keenable import KeenableWebSearch + +pipe = Pipeline() +pipe.add_component("search", KeenableWebSearch(top_k=5)) +pipe.add_component("prompt", PromptBuilder(template="Answer using:\n{{ documents }}")) +pipe.connect("search.documents", "prompt.documents") +``` + +### KeenableFetcher + +`KeenableFetcher` fetches web pages via Keenable and returns their main content +as markdown. Pair it with `KeenableWebSearch`: discover URLs with search, then +read the full pages with the fetcher. + +```python +from haystack_integrations.components.fetchers.keenable import KeenableFetcher + +fetcher = KeenableFetcher() +result = fetcher.run(urls=["https://example.com/article"]) +print(result["documents"][0].content) +``` + +Non-`http(s)` and private/internal URLs are rejected before sending, and (like +`LinkContentFetcher`) failed URLs are skipped by default — set +`raise_on_failure=True` to surface errors instead. + +## License + +`keenable-haystack` is distributed under the terms of the MIT license. diff --git a/logos/keenable.png b/logos/keenable.png new file mode 100644 index 0000000000000000000000000000000000000000..fb394bfcb542de9fa0679753b04ec31c748670a1 GIT binary patch literal 12030 zcmeIY_g7QT6EF-2f(TLtr3Mg0X(Aew8tF(E=_Rxn=}Hp_)k2k~0@6DOgceGK08wf* zbfh;?dVnC11PFu&zURE>{TH6+oX-z??(W^WGka%dXJ=+7*4R*+nURN)ii(O^S4ZOs z6%}>zd1JVE_9g-i>^pl<`##ZDqpBX@TR$5-fLQ82GccgKeb#57qKnqJ~%WWQc+3!>1sTB8c4l0N1x59IoIeh5)Tqve0MG%`IR=Is>;MpZT^;NH5K?zQ z&nH2E6e{WuF$wR^zM`gOU{$&BKSZ1OZEl5fw6fg*OUpHH&t+yF(~aB>zpr$9~f zcJO>M0whdLbAyfdvd}pNRzS$b{~=<(#0rR=v}8D^aJE#r|CV|#f&aMr|E8|q7ZL|M zd}mKdfT*_$!TS|tSvH4r)^r;fH%$q5s4{10;3W% zS{k_4XCJ#@4K&;e>{J3e79{PY@BWie){@gy$&p$lD+;~^pAJ7^CZYQ5>>HlC{L)BCS7i(G ze7thGyf5mKJ-lXd7ndlE@@Jo_tjAoj1{B+avJ6(`B z+khC%I?SSm7r)oe(Y*LD1w~0|KFpE(&1r6{Cj&hRGfI&N7ePc`RHcjD5I5eZc53sB zQj+!|KTpYXHcD(%iL`MYteQz;n}TIEhMBmiQsAVV?GP)Mog(3d)pd%N_1pf&iMdAY znC!xIz&dA2+HKq|&d9>AMsx zg4p`Pe#QXiH3y5stVl6Jv!bM-Q(U4(u{(VTO1oPMs|zeMgo z-dm0jP%B}&=#F+V1>eGpjLI8v|8@{Yi69d?%qTvN?-df` z)P^_dTaNJ56rdF8h=dll48;*%*GJ?jpof~2P+MEYMo3jywN7+Gy-ngc1Lp_OFfC>* z_}0?y%|Cd@)bZ#dcit540U0=yUGg{myeML`w}JDI{h(HYhL&5*c;J)+sivjGO7+nPEFBM(6Oa;B-%~5vQ68?4?q)4Y}86l+&b~ z6j^nQ^7AirY5i09OPzvt;~v_u26{>E{_Iv@b?6fE!GF~^s#L>i<%~mr=3Wnas=ma^ z-xBp2BzuX|Bf>H;Tget>*lw;LEi%>KFvNPD*EMFgWYJ6c5920VwhMjX>k@_(#uo=> z-x=ZHFE%PmbrsAkTda-246k5rtpx!K4Jo#j700kTOqhJ!J&dQh|7ax-D3uJUIXPiJ8qevT$-c=uvLuY+1U7;jj9nkWW0BoO9E$ zn~0K_xvx!nnpIhG9`6_y{`Q>Bd@u}f@AyE^s^O*DR{=fk3v$6n|s99W%{;AZ)eY`R-wE#_4k7M zgYP?(oFiv$zalUbSQ6!BB;J$Fn@t`1yM>KkQ1AXMJDMOG2ot&Os^i}EwA=Y#!_N!T zk>DgWQAs^5EEnObaLCoEo^eE@Q-F7!IY=VwLEeJY%}Squ&=*IrEQvh77k1} z>g#r#iEkMG#&8SV*Z=+_&?!XBQf=I93c~SG->Fr0*KuWTcR0L zR1(wW#pwE*v7Ql1yj^#=MXD&p1<-VVuMidAfDe9(5;ofvof^{0#R&7}!#0Bhm&Jd> zeY5ZdfzpPdT9bHK!(cybQo{_{D!$uCZni>KHG0e2oj!bb0(!pfXX#tOz^NZGIm8`W zjkRTe%sVmgfKr=&rR9rJAaY30^iW)cfc?=TICBr~%1!>jUo{6KZDtcQCBO?oEF0-~ zy;P*$$RCH3k+$}5iu7q~Svp|rT0%~?i@OAP88%4W%eSA3Q zueRdj)LqzoxTXG=0N)SGaSnBD!6CDb@V4d9vEYR>9TsBb)QD(RwaZ&+6FwmeK-aqr zXM4i&f`t7 zlLu|50gI8r3&NU1NsY){gF?+m`K)w>su${94UF34m8S#xHSGmB@qkyWNkc;42^YPr zyIYXcCGbX6eQ9Hu%^>}#X0UzQ{98MMJJjnAeoVB#O5fXVJZpOr9Sa{32yo@(-F6va z7Ty9rcJ_o{h&;Ze5qi;S&}`D5gV|T zy8`)i7NvyUH$+=SZAC$2R`Gk0U(QU;WMdh8R2--b zw|mVHY7a>8Ecf7_;1lv`4Cpqfv@PRj<&xBO_bi!k_U%IhOZMG$!x4J%zz4<34cB20 z;prF-Xx!w-ZP!m_3x97B?(tb^htU->v%(6=M3Z%ghF{gqr$fB0L>HfePeMn0z1Lx7 z^^JOIxD#6Dl!o}ZJVa36s5o(=l{s|Y22X$WaUEMg6+PH``OhcG(P@G4Hy{Yj@I{O# zkASPbzZ+PenC>L>PW#8B`kJX*utV(;;J5Tw#_vl3Fu>Zb*zgc|iV4Q>F?{*zrhqZDwX+>Yy3loQmrY@4n9 z=H>Kt^&s!JHNE4>S{BZiHDMjCi2=@1`%Z%uvj;PNM5i^qb$E0mAlW@~E%!p5Y~j%y zPy5U(9QSB>Xvf6I`1>J*F^{0VDkNm2cyJk41I3P%V2jPGtVdddOxK4B+C0#q^6SH) zHsvPjrwu11lON}8>;ZRGYvs)bi5WNZ^v1K6z_{N-#Lb;mfZ?>%T-J~-v7{BkC2f1d zJ0TAs7`h+Z_xYh-I~ehFeDqNd@cD@4G}2pQ4&|RZ-v)bpazq%1cWBwq6L_`^N=SFr z;*{y-@HGm9?*m**0j%|){z>3KsVcLSqTkT1rz8eDy%y}LHrlSI^|)ddhJUk5p%=7h z*SC(3X=*IG0t~KTqxW z?2IS)5`XjD-|z74Hj)-#-}?61y)*V&vwGH~QwA zi(t_JyhzpVxD6iDoo*`y-Q;NR;a1UaN*t28&wKQDY3^g&t7*Od9Fq6L!(a8wWPD?^ z+G9vsK=K34iJ<9iiPsYcAsx<~$31W=YNN>KG)tWsiyXx42Gpwk>emdr00q*q5W>x@ zTi)@bN0eQWcf)VdIyPJNI6L7VO_Q{iHAX)I%15@y>pOz|ys2ST6=5yEJFv?$-rS`W zVt}s4uTG>csmZa@(EWW~ZIy#7?N9C)G0^-`>v6*VVS>TmX*ALZJBw^Sks+-$?$AAi z70q}xYZCDlNon+-{(+yNYn6XL6X}iOn^7*Qkhp z)jX{>9uN8ox<|cBM0W&~POJ!5J-N0k;vupBkhv}PXsFXDs=BmedF%SSXL<|pS_25W zVTxr{vI1_lz7Q=S0&D;N>>7N-Dg7$`t8bv8eCNC~QE?G3ONr0+5|hvd=$r}Q5K!yx z#st<6xRD!t6tpFE^#Iz^E0m)Y@EMM_pFBzRi0)%h)HAWazN>SWA zOyUVXEz(Ba9yE@rA2O$+oYsx5 zbGO&7O^Hb^vzs$&zcyP^JLA!z?2E3XD7IM;^37R2) z>6V1jOe(VavzYeAh?Stu57YG#NagR?0`n!(&*!-=fmu$hF(M$EQ~KD&#fA?oodb$n z#!vd!(ZDy3HTHAGUcWzEwu23USIl+l9&tL~LTzT}IlGWi_3ajG?c}Hs$#xHI0^6DN z)tWCTG{0>pC}2rA-+fmJr1 zx0PWY$l%hBWWv%q>pKr&thwnKe{O)oHCO*$I}MKBD)C*d@}ZdD%<Q^AK* zy5?~ajAv`*z0at~B`hs z-O0$>@7J6q8r>&8ptf7BHgk1&x&SQw{3L`~l{^U5h8J>Ef3lraqz;e5Wl;2NSPAqz2s? zmY$sdmeS7m-$wpVeX~q&^f*m?mk|g_RN5VSaps22z=1D2nKLo(xU+8=sr-xZn{1s^ zz=quk%}GZD3uRH&1@-aLGev!UEC@Tj;jw=RCrZuq1ay+$tqP=EATGH358hf>cAu!0 z>g5Y9ejof#b_$XXuBo?NyJ0)USL8pR&CQCb192w3@*koqFE>fsm${!+M*eFhJU6p! zDd9kvYw=Hw)?)gYq(XVBl^9|&AGntX8Hk(s`tmHl0F|G5wV;B)tAuuK!#-Ke9~HJP z9JI}=V1Lg$aiqK|JFG&%&4Hp`$NV!3tew}&^Baq*WO@yBkd4=Ac01#q`}Doa0%I|KW|X8$qyvV5w2k0^q2gwy*dmH-(wu&J&ldyrgmx@BDek9lKc zXYxr?($j$CC=e$U&?GYB#l;1X3AGKRc7k&AD>fK~@F5qJ{uRb@H8^v!1Bz7Ha$e}6 zD4H)9e_0h3DdS$c?uuxf+D>>Cxk$hM9H@hWrw zB@^2w)hX@~`s=L{E>umX-X@=>} za<9U@B`>({I!9tkj5t>w-FjQ4BKOYG*+crHDSkOBScuQNi7gUATX>muMu+Uf*p}gi zYxewv!H}x7bZvf%x3M^>Cq$>ot8h2J&u4+S1kqM#0De+UJ)u8T{0jTpJE)+)^i9$K z+Dra&Bx`&EuYZT~ORw;)%#3`aPC_PBL*Nj~<6e>JZ+i;II0r8zyaxGm#M}jK0&EYn z{07w8!3B&K&GiXyAaPj! zg3|cA);_%V*kGw~kP(VH%eIcN#sEQ0V_6LW>dFas4a!WF*9ILs-OB3tr{I4}S2ewY zh3dO3F3Sn+&LKR;lgUlY{uLh{g^mUZW{sBA2o&kk`I(09Yos{i$6*g1*Q%jzkVaLX zGP;sSANs&c6fSG<0s@SymDP$9?QiQAO}c2Iz!G=ysC(mSGyax=wg)fPNwrhQSB7Q2 zOy=esbmj_YL1piIt+(Ai8Rb(3qU?)4nj1)+1ix2JIC{Ww4HyH}wJN4YFC+-p-`}~# z@Ra^6HBs!0-KTUkzHzPR+74+viy;0eH3SU)eH}f{ob;h8)J!IYuQL6y6VJu2o;zf0 zov6(3bT%U}>lDM?Fu^R6spCZuS@psoZVq7xP}PRhxg`-O6C5C75fOp3McRw#IMu+Y0s2u#6~rmx>fP+4#Heym8lD{lOkQ7R|| zt>Hap-ixtrQd{bmVVAPsRs$-|Ul!cBu1lYi#`|eT5{f=p)ZX$W7Grcf3GuI71H7H( zUm(3iN1FWuu*(&VbH+%yZtVqK62D4bCAh;o=J>G;)S9#%!i};GTyW2ji@Vh#HO0w$ zZ$t$N^ZwZXq(L+P1uf$rrmhEDKHsib#Dv@t37sVnWV$#4#^|T7u+>rX``T?jiLr}A zV1-66Z1-ByzZvFUPdIao+PWz?)t8nlmnXt6ou^?`zAFC)hIef+bFw{H6Ybzvu*Bc7u z1C~_wTitbSI`z5QIGf*&;dNujX2fj$HMCe3Thrqs;&1z;C7RbJQ6PE5WueQYRRY{^ zO+3XyEVb2n5>2H4SyUF!H!k6!ESUiEIM3upLDRk9>3W$Y^`8pj%G0rYQ#Po9js8U` z8tEL#37KCOH{+F5gyiU>{0aJ~2G`qn?G-OJjD$OjH(xpW5b9Ag7fr^=Dl|Z!Rm>4M z0xnsV6v#TdtsE#-K%?XcVcOFrUQ#Kt+^#a;q(<%KaO(LRw2acA_Zx}B!MeZ0_oAP1 zh)*er+ReCZD>k^6lJqlUCjG<7P6w{3F(=EOP%R=;gu!#j1bXX-8;sH=a2M-e=Lz$S ziDA~du3m>#CC?`3g(6QH#~{kIw&7^ zok>=C!GT|LUvEusnZv@CTRgtZ9IQZubN1)2(nUJ4ieso`556a@5q4JN7FmnyS!^~8 zo{@dr#>I08C3R^x=ZEXRD*A*seeP*mapq4r%f`Rt*_~4ZMzu!6|H)gC?f*|UJ;>?8v`j#kDy`HowZjldmOHfv$1 zjfRO$cYikV1FBmOjn%>I54|-A&GYR=^LN6Cwkf4YbY|j^K8=)4@4-Q4+%ORFaPcnQ}PvR;sVPWU8&iJC7=P~Ka>xBM{&9#YF zf)9q>OZsA}E(@#oHWM*^O6~MEZ*VvZd1O(|9B%K?-NJObMa$pko!&8D|86>!_!UK9 z+IZ73pS0UV>9`>03?CDpl)ily+}DU0Tj#IQbOj9d2r6jf<7efX_6N9jjb_&BnMrwR zSvQv|JG!^+rZ^7MmD12qO`otiO+8j4DlnOr>cL=;!AfA zif6>A*G1vW9T_XL6_rC=-xTJpYii?rG6|SAokt(zF$vh-a>23cnC71)lJh-yCMEJl z$|iba;I^q^cH0w;l`1D;gSuvNjGo)1Z8Y9NJ@(ivY4EDT-G3c?OoZKqSC7%-FP&3R z1bC6r>i$GO4_k~~{@R7&B`W=4m}B=cxlc-r$5Sw+3hm>++l9+Eg`Q|D-Zy6bGC(sX4SIZqc%zLNMv@R_X)U<0X|@RA z;N6&sx``B8B8`QQ3gouBgkSy#&RX8V^9Xk~$h~C_vrL7Mi5bqn!s%ZZ-A=lI=T8SU zGADoNbSPR=IkA5xj^|b#5wJb-t&V>d<>y_do`OqHF@W)4?`$^D$R-oxN>3Bm4XmP)PZh7gnp%Y~KBERwiVq?;gMTd4ldbwyom{S*CYt%=k5tlas= zgMfa2=@=d#qEMg8RaaHFf=)^`n<_^?+;c-tMh^sZU_-NDSsV|HSmT@(Vh$h@%@^Yuv7 z#Yk`_gcG=EQf45x7t;lN;zftDGe#roIbaoYiNh{A%P-UHHxP#^o2r+&$VJ~0Q&Oj= zMO$(OZYpX8C~azeqpRlyH{ZY! zRR55LqUYr~5h9?72iKmmofq&N(=o7yOIQn^7vnsjp^fU>J m;D6-;iW?W literal 0 HcmV?d00001