<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <id>https://blog.wakaplus.com/</id>
  <title type="text">waka&apos;s blog</title>
  <subtitle type="text">waka的博客</subtitle>
  <updated>2025-04-16T00:00:00.000Z</updated>
  <author><name>waka</name></author>
  <link rel="alternate" href="https://blog.wakaplus.com/"/>
  <link rel="self" href="https://blog.wakaplus.com/atom.xml"/>
  <generator uri="https://github.com/CuteLeaf/Firefly">Firefly v6.16.8</generator>
    <entry>
      <id>https://blog.wakaplus.com/posts/deno-api-proxy/</id>
      <title type="text">使用Deno构建多种大模型AI API代理</title>
      <published>2025-04-16T00:00:00.000Z</published>
      <updated>2025-04-16T00:00:00.000Z</updated>
      <author><name>waka</name></author>
      <link rel="alternate" href="https://blog.wakaplus.com/posts/deno-api-proxy/"/>
      <summary type="text">使用deno将多种AI API的代理整合到一个服务中(openai, gemini等)</summary>
      <content type="html"><![CDATA[<section><h2>使用 Deno 搭建大模型 API 反向代理服务<a href="#使用-deno-搭建大模型-api-反向代理服务"><span>#</span></a></h2><section><h3>背景介绍<a href="#背景介绍"><span>#</span></a></h3><p>随着各大 AI 公司相继推出自己的语言模型 API 服务,开发者在使用这些服务时经常会遇到网络访问的问题。本文将介绍如何使用 Deno 搭建一个反向代理服务器,用于代理访问各种 AI 模型的 API 接口。</p></section><section><h3>为什么使用 Deno<a href="#为什么使用-deno"><span>#</span></a></h3><p>众所周知cloudflare workers可以作为一个非常好的反向代理服务,但是它的免费版有很多限制,比如每天的请求次数有限制,还有会暴露你的ip地址等。
Deno是一个安全的运行时环境,它的安全性和性能都非常好,而且它的部署也非常简单。</p></section><section><h3>更新日志<a href="#更新日志"><span>#</span></a></h3><ul>
<li>2025.1.10 更新: 避免出现 “Unable to submit request because it must have a text parameter”</li>
<li>2025.4.7 更新: 添加 mistral 代理；添加 gemini 的 gemma 系列模型；更换默认模型为 gemini-2.0-flash</li>
<li>2025.4.16 更新: 跟随上游更新，添加工具调用</li>
<li>2025.5.2 更新: 添加 seed 参数支持</li>
<li>2025.8.9 更新: 修改默认模型为 gemini-2.5-flash</li>
</ul></section><section><h3>代码实现<a href="#代码实现"><span>#</span></a></h3><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>import</span><span> { </span><span>serve</span><span> } </span><span>from</span><span> </span><span>"https://deno.land/std/http/server.ts"</span><span>;</span></div></div><div><div><div>2</div></div><div><span>import</span><span> { </span><span>Buffer</span><span> } </span><span>from</span><span> </span><span>"node:buffer"</span><span>;</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>const</span><span> </span><span>apiMapping</span><span> </span><span>=</span><span> {</span></div></div><div><div><div>5</div></div><div><span>  </span><span>'/discord'</span><span>:</span><span> </span><span>'https://discord.com/api'</span><span>,</span></div></div><div><div><div>6</div></div><div><span>  </span><span>'/telegram'</span><span>:</span><span> </span><span>'https://api.telegram.org'</span><span>,</span></div></div><div><div><div>7</div></div><div><span>  </span><span>'/openai'</span><span>:</span><span> </span><span>'https://api.openai.com'</span><span>,</span></div></div><div><div><div>8</div></div><div><span>  </span><span>'/claude'</span><span>:</span><span> </span><span>'https://api.anthropic.com'</span><span>,</span></div></div><div><div><div>9</div></div><div><span>  </span><span>'/meta'</span><span>:</span><span> </span><span>'https://www.meta.ai/api'</span><span>,</span></div></div><div><div><div>10</div></div><div><span>  </span><span>'/groq'</span><span>:</span><span> </span><span>'https://api.groq.com/openai'</span><span>,</span></div></div><div><div><div>11</div></div><div><span>  </span><span>'/xai'</span><span>:</span><span> </span><span>'https://api.x.ai'</span><span>,</span></div></div><div><div><div>12</div></div><div><span>  </span><span>'/cohere'</span><span>:</span><span> </span><span>'https://api.cohere.ai'</span><span>,</span></div></div><div><div><div>13</div></div><div><span>  </span><span>'/huggingface'</span><span>:</span><span> </span><span>'https://api-inference.huggingface.co'</span><span>,</span></div></div><div><div><div>14</div></div><div><span>  </span><span>'/together'</span><span>:</span><span> </span><span>'https://api.together.xyz'</span><span>,</span></div></div><div><div><div>15</div></div><div><span>  </span><span>'/novita'</span><span>:</span><span> </span><span>'https://api.novita.ai'</span><span>,</span></div></div><div><div><div>16</div></div><div><span>  </span><span>'/portkey'</span><span>:</span><span> </span><span>'https://api.portkey.ai'</span><span>,</span></div></div><div><div><div>17</div></div><div><span>  </span><span>'/fireworks'</span><span>:</span><span> </span><span>'https://api.fireworks.ai'</span><span>,</span></div></div><div><div><div>18</div></div><div><span>  </span><span>'/openrouter'</span><span>:</span><span> </span><span>'https://openrouter.ai/api'</span><span>,</span></div></div><div><div><div>19</div></div><div><span>  </span><span>'/nvidia'</span><span>:</span><span> </span><span>'https://integrate.api.nvidia.com'</span><span>,</span></div></div><div><div><div>20</div></div><div><span>  </span><span>'/cerebras'</span><span>:</span><span> </span><span>'https://api.cerebras.ai'</span><span>,</span></div></div><div><div><div>21</div></div><div><span>  </span><span>'/sambanova'</span><span>:</span><span> </span><span>'https://api.sambanova.ai'</span><span>,</span></div></div><div><div><div>22</div></div><div><span>  </span><span>'/gemini'</span><span>:</span><span> </span><span>'https://generativelanguage.googleapis.com'</span><span>,</span></div></div><div><div><div>23</div></div><div><span>  </span><span>'/mistral'</span><span>:</span><span> </span><span>'https://api.mistral.ai'</span></div></div><div><div><div>24</div></div><div><span>};</span></div></div><div><div><div>25</div></div><div>
</div></div><div><div><div>26</div></div><div><span>class</span><span> </span><span>HttpError</span><span> </span><span>extends</span><span> </span><span>Error</span><span> {</span></div></div><div><div><div>27</div></div><div><span>  </span><span>constructor</span><span><span>(</span><span>message</span><span>, </span><span>status</span><span>) {</span></span></div></div><div><div><div>28</div></div><div><span>    </span><span>super</span><span>(message);</span></div></div><div><div><div>29</div></div><div><span>    </span><span>this</span><span>.</span><span>name</span><span> </span><span>=</span><span> </span><span>this</span><span>.</span><span>constructor</span><span>.</span><span>name</span><span>;</span></div></div><div><div><div>30</div></div><div><span>    </span><span>this</span><span>.</span><span>status</span><span> </span><span>=</span><span> status;</span></div></div><div><div><div>31</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>32</div></div><div><span>}</span></div></div><div><div><div>33</div></div><div>
</div></div><div><div><div>34</div></div><div><span>const</span><span> </span><span>fixCors</span><span> </span><span>=</span><span><span> ({ </span><span>headers</span><span>, </span><span>status</span><span>, </span><span>statusText</span><span> }) </span></span><span>=&gt;</span><span> {</span></div></div><div><div><div>35</div></div><div><span><span>  </span></span><span>headers </span><span>=</span><span> </span><span>new</span><span> </span><span>Headers</span><span>(headers);</span></div></div><div><div><div>36</div></div><div><span><span>  </span></span><span>headers.</span><span>set</span><span>(</span><span>"Access-Control-Allow-Origin"</span><span>, </span><span>"*"</span><span>);</span></div></div><div><div><div>37</div></div><div><span>  </span><span>return</span><span> { headers, status, statusText };</span></div></div><div><div><div>38</div></div><div><span>};</span></div></div><div><div><div>39</div></div><div>
</div></div><div><div><div>40</div></div><div><span>const</span><span> </span><span>handleOPTIONS</span><span> </span><span>=</span><span> </span><span>async</span><span> () </span><span>=&gt;</span><span> {</span></div></div><div><div><div>41</div></div><div><span>  </span><span>return</span><span> </span><span>new</span><span> </span><span>Response</span><span>(</span><span>null</span><span>, {</span></div></div><div><div><div>42</div></div><div><span><span>    </span></span><span>headers</span><span>:</span><span> {</span></div></div><div><div><div>43</div></div><div><span>      </span><span>"Access-Control-Allow-Origin"</span><span>:</span><span> </span><span>"*"</span><span>,</span></div></div><div><div><div>44</div></div><div><span>      </span><span>"Access-Control-Allow-Methods"</span><span>:</span><span> </span><span>"*"</span><span>,</span></div></div><div><div><div>45</div></div><div><span>      </span><span>"Access-Control-Allow-Headers"</span><span>:</span><span> </span><span>"*"</span><span>,</span></div></div><div><div><div>46</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>47</div></div><div><span><span>  </span></span><span>});</span></div></div><div><div><div>48</div></div><div><span>};</span></div></div><div><div><div>49</div></div><div>
</div></div><div><div><div>50</div></div><div><span>const</span><span> </span><span>BASE_URL</span><span> </span><span>=</span><span> </span><span>"https://generativelanguage.googleapis.com"</span><span>;</span></div></div><div><div><div>51</div></div><div><span>const</span><span> </span><span>API_VERSION</span><span> </span><span>=</span><span> </span><span>"v1beta"</span><span>;</span></div></div><div><div><div>52</div></div><div><span>const</span><span> </span><span>API_CLIENT</span><span> </span><span>=</span><span> </span><span>"genai-js/0.21.0"</span><span>;</span></div></div><div><div><div>53</div></div><div>
</div></div><div><div><div>54</div></div><div><span>const</span><span> </span><span>makeHeaders</span><span> </span><span>=</span><span><span> (</span><span>apiKey</span><span>, </span><span>more</span><span>) </span></span><span>=&gt;</span><span> ({</span></div></div><div><div><div>55</div></div><div><span>  </span><span>"x-goog-api-client"</span><span>:</span><span> </span><span>API_CLIENT</span><span>,</span></div></div><div><div><div>56</div></div><div><span>  </span><span>...</span><span>(apiKey </span><span>&amp;&amp;</span><span> { </span><span>"x-goog-api-key"</span><span>:</span><span> apiKey }),</span></div></div><div><div><div>57</div></div><div><span>  </span><span>...</span><span>more</span></div></div><div><div><div>58</div></div><div><span>});</span></div></div><div><div><div>59</div></div><div>
</div></div><div><div><div>60</div></div><div><span>async</span><span> </span><span>function</span><span> </span><span>handleModels</span><span><span>(</span><span>apiKey</span><span>) {</span></span></div></div><div><div><div>61</div></div><div><span>  </span><span>const</span><span> </span><span>response</span><span> </span><span>=</span><span> </span><span>await</span><span> </span><span>fetch</span><span>(</span><span>`</span><span>${</span><span>BASE_URL</span><span>}</span><span>/</span><span>${</span><span>API_VERSION</span><span>}</span><span>/models`</span><span>, {</span></div></div><div><div><div>62</div></div><div><span><span>    </span></span><span>headers</span><span>:</span><span> </span><span>makeHeaders</span><span>(apiKey),</span></div></div><div><div><div>63</div></div><div><span><span>  </span></span><span>});</span></div></div><div><div><div>64</div></div><div><span>  </span><span>let</span><span> { body } </span><span>=</span><span> response;</span></div></div><div><div><div>65</div></div><div><span>  </span><span>if</span><span> (response.</span><span>ok</span><span>) {</span></div></div><div><div><div>66</div></div><div><span>    </span><span>const</span><span> { </span><span>models</span><span> } </span><span>=</span><span> </span><span>JSON</span><span>.</span><span>parse</span><span>(</span><span>await</span><span> response.</span><span>text</span><span>());</span></div></div><div><div><div>67</div></div><div><span><span>    </span></span><span>body </span><span>=</span><span> </span><span>JSON</span><span>.</span><span>stringify</span><span>({</span></div></div><div><div><div>68</div></div><div><span><span>      </span></span><span>object</span><span>:</span><span> </span><span>"list"</span><span>,</span></div></div><div><div><div>69</div></div><div><span><span>      </span></span><span>data</span><span>:</span><span> models.</span><span>map</span><span><span>(({ </span><span>name</span><span> }) </span></span><span>=&gt;</span><span> ({</span></div></div><div><div><div>70</div></div><div><span><span>        </span></span><span>id</span><span>:</span><span> name.</span><span>replace</span><span>(</span><span>"models/"</span><span>, </span><span>""</span><span>),</span></div></div><div><div><div>71</div></div><div><span><span>        </span></span><span>object</span><span>:</span><span> </span><span>"model"</span><span>,</span></div></div><div><div><div>72</div></div><div><span><span>        </span></span><span>created</span><span>:</span><span> </span><span>0</span><span>,</span></div></div><div><div><div>73</div></div><div><span><span>        </span></span><span>owned_by</span><span>:</span><span> </span><span>""</span><span>,</span></div></div><div><div><div>74</div></div><div><span><span>      </span></span><span>})),</span></div></div><div><div><div>75</div></div><div><span><span>    </span></span><span>}, </span><span>null</span><span>, </span><span>"  "</span><span>);</span></div></div><div><div><div>76</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>77</div></div><div><span>  </span><span>return</span><span> </span><span>new</span><span> </span><span>Response</span><span>(body, </span><span>fixCors</span><span>(response));</span></div></div><div><div><div>78</div></div><div><span>}</span></div></div><div><div><div>79</div></div><div>
</div></div><div><div><div>80</div></div><div><span>const</span><span> </span><span>DEFAULT_EMBEDDINGS_MODEL</span><span> </span><span>=</span><span> </span><span>"text-embedding-004"</span><span>;</span></div></div><div><div><div>81</div></div><div><span>async</span><span> </span><span>function</span><span> </span><span>handleEmbeddings</span><span><span>(</span><span>req</span><span>, </span><span>apiKey</span><span>) {</span></span></div></div><div><div><div>82</div></div><div><span>  </span><span>if</span><span> (</span><span>typeof</span><span> req.</span><span>model</span><span> </span><span>!==</span><span> </span><span>"string"</span><span>) {</span></div></div><div><div><div>83</div></div><div><span>    </span><span>throw</span><span> </span><span>new</span><span> </span><span>HttpError</span><span>(</span><span>"model is not specified"</span><span>, </span><span>400</span><span>);</span></div></div><div><div><div>84</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>85</div></div><div><span>  </span><span>let</span><span> model;</span></div></div><div><div><div>86</div></div><div><span>  </span><span>if</span><span> (req.</span><span>model</span><span>.</span><span>startsWith</span><span>(</span><span>"models/"</span><span>)) {</span></div></div><div><div><div>87</div></div><div><span><span>    </span></span><span>model </span><span>=</span><span> req.</span><span>model</span><span>;</span></div></div><div><div><div>88</div></div><div><span><span>  </span></span><span>} </span><span>else</span><span> {</span></div></div><div><div><div>89</div></div><div><span>  </span><span>if</span><span> (</span><span>!</span><span>req.</span><span>model</span><span>.</span><span>startsWith</span><span>(</span><span>"gemini-"</span><span>)) {</span></div></div><div><div><div>90</div></div><div><span><span>      </span></span><span>req.</span><span>model</span><span> </span><span>=</span><span> </span><span>DEFAULT_EMBEDDINGS_MODEL</span><span>;</span></div></div><div><div><div>91</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>92</div></div><div><span><span>    </span></span><span>model </span><span>=</span><span> </span><span>"models/"</span><span> </span><span>+</span><span> req.</span><span>model</span><span>;</span></div></div><div><div><div>93</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>94</div></div><div><span>  </span><span>if</span><span> (</span><span>!</span><span>Array.</span><span>isArray</span><span>(req.</span><span>input</span><span>)) {</span></div></div><div><div><div>95</div></div><div><span><span>    </span></span><span>req.</span><span>input</span><span> </span><span>=</span><span> [ req.</span><span>input</span><span> ];</span></div></div><div><div><div>96</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>97</div></div><div><span>  </span><span>const</span><span> </span><span>response</span><span> </span><span>=</span><span> </span><span>await</span><span> </span><span>fetch</span><span>(</span><span>`</span><span>${</span><span>BASE_URL</span><span>}</span><span>/</span><span>${</span><span>API_VERSION</span><span>}</span><span>/</span><span>${</span><span>model</span><span>}</span><span>:batchEmbedContents`</span><span>, {</span></div></div><div><div><div>98</div></div><div><span><span>    </span></span><span>method</span><span>:</span><span> </span><span>"POST"</span><span>,</span></div></div><div><div><div>99</div></div><div><span><span>    </span></span><span>headers</span><span>:</span><span> </span><span>makeHeaders</span><span>(apiKey, { </span><span>"Content-Type"</span><span>:</span><span> </span><span>"application/json"</span><span> }),</span></div></div><div><div><div>100</div></div><div><span><span>    </span></span><span>body</span><span>:</span><span> </span><span>JSON</span><span>.</span><span>stringify</span><span>({</span></div></div><div><div><div>101</div></div><div><span>      </span><span>"requests"</span><span>:</span><span> req.</span><span>input</span><span>.</span><span>map</span><span><span>(</span><span>text</span><span> </span></span><span>=&gt;</span><span> ({</span></div></div><div><div><div>102</div></div><div><span><span>        </span></span><span>model,</span></div></div><div><div><div>103</div></div><div><span><span>        </span></span><span>content</span><span>:</span><span> { </span><span>parts</span><span>:</span><span> { text } },</span></div></div><div><div><div>104</div></div><div><span><span>        </span></span><span>outputDimensionality</span><span>:</span><span> req.</span><span>dimensions</span><span>,</span></div></div><div><div><div>105</div></div><div><span><span>      </span></span><span>}))</span></div></div><div><div><div>106</div></div><div><span><span>    </span></span><span>})</span></div></div><div><div><div>107</div></div><div><span><span>  </span></span><span>});</span></div></div><div><div><div>108</div></div><div><span>  </span><span>let</span><span> { body } </span><span>=</span><span> response;</span></div></div><div><div><div>109</div></div><div><span>  </span><span>if</span><span> (response.</span><span>ok</span><span>) {</span></div></div><div><div><div>110</div></div><div><span>    </span><span>const</span><span> { </span><span>embeddings</span><span> } </span><span>=</span><span> </span><span>JSON</span><span>.</span><span>parse</span><span>(</span><span>await</span><span> response.</span><span>text</span><span>());</span></div></div><div><div><div>111</div></div><div><span><span>    </span></span><span>body </span><span>=</span><span> </span><span>JSON</span><span>.</span><span>stringify</span><span>({</span></div></div><div><div><div>112</div></div><div><span><span>      </span></span><span>object</span><span>:</span><span> </span><span>"list"</span><span>,</span></div></div><div><div><div>113</div></div><div><span><span>      </span></span><span>data</span><span>:</span><span> embeddings.</span><span>map</span><span><span>(({ </span><span>values</span><span> }, </span><span>index</span><span>) </span></span><span>=&gt;</span><span> ({</span></div></div><div><div><div>114</div></div><div><span><span>        </span></span><span>object</span><span>:</span><span> </span><span>"embedding"</span><span>,</span></div></div><div><div><div>115</div></div><div><span><span>        </span></span><span>index,</span></div></div><div><div><div>116</div></div><div><span><span>        </span></span><span>embedding</span><span>:</span><span> values,</span></div></div><div><div><div>117</div></div><div><span><span>      </span></span><span>})),</span></div></div><div><div><div>118</div></div><div><span><span>      </span></span><span>model</span><span>:</span><span> req.</span><span>model</span><span>,</span></div></div><div><div><div>119</div></div><div><span><span>    </span></span><span>}, </span><span>null</span><span>, </span><span>"  "</span><span>);</span></div></div><div><div><div>120</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>121</div></div><div><span>  </span><span>return</span><span> </span><span>new</span><span> </span><span>Response</span><span>(body, </span><span>fixCors</span><span>(response));</span></div></div><div><div><div>122</div></div><div><span>}</span></div></div><div><div><div>123</div></div><div>
</div></div><div><div><div>124</div></div><div><span>const</span><span> </span><span>DEFAULT_MODEL</span><span> </span><span>=</span><span> </span><span>"gemini-2.5-flash"</span><span>;</span></div></div><div><div><div>125</div></div><div><span>async</span><span> </span><span>function</span><span> </span><span>handleCompletions</span><span><span>(</span><span>req</span><span>, </span><span>apiKey</span><span>) {</span></span></div></div><div><div><div>126</div></div><div><span>  </span><span>let</span><span> model </span><span>=</span><span> </span><span>DEFAULT_MODEL</span><span>;</span></div></div><div><div><div>127</div></div><div><span>  </span><span>switch</span><span> (</span><span>true</span><span>) {</span></div></div><div><div><div>128</div></div><div><span>    </span><span>case</span><span> </span><span>typeof</span><span> req.</span><span>model</span><span> </span><span>!==</span><span> </span><span>"string"</span><span>:</span></div></div><div><div><div>129</div></div><div><span>      </span><span>break</span><span>;</span></div></div><div><div><div>130</div></div><div><span>    </span><span>case</span><span> req.</span><span>model</span><span>.</span><span>startsWith</span><span>(</span><span>"models/"</span><span>):</span></div></div><div><div><div>131</div></div><div><span><span>      </span></span><span>model </span><span>=</span><span> req.</span><span>model</span><span>.</span><span>substring</span><span>(</span><span>7</span><span>);</span></div></div><div><div><div>132</div></div><div><span>      </span><span>break</span><span>;</span></div></div><div><div><div>133</div></div><div><span>    </span><span>case</span><span> req.</span><span>model</span><span>.</span><span>startsWith</span><span>(</span><span>"gemini-"</span><span>):</span></div></div><div><div><div>134</div></div><div><span>    </span><span>case</span><span> req.</span><span>model</span><span>.</span><span>startsWith</span><span>(</span><span>"gemma-"</span><span>):</span></div></div><div><div><div>135</div></div><div><span>    </span><span>case</span><span> req.</span><span>model</span><span>.</span><span>startsWith</span><span>(</span><span>"learnlm-"</span><span>):</span></div></div><div><div><div>136</div></div><div><span><span>      </span></span><span>model </span><span>=</span><span> req.</span><span>model</span><span>;</span></div></div><div><div><div>137</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>138</div></div><div><span>  </span><span>let</span><span> body </span><span>=</span><span> </span><span>await</span><span> </span><span>transformRequest</span><span>(req);</span></div></div><div><div><div>139</div></div><div><span>  </span><span>const</span><span> </span><span>extra</span><span> </span><span>=</span><span> req.</span><span>extra_body</span><span>?.</span><span>google</span></div></div><div><div><div>140</div></div><div><span>  </span><span>if</span><span> (extra) {</span></div></div><div><div><div>141</div></div><div><span>    </span><span>if</span><span> (extra.</span><span>safety_settings</span><span>) {</span></div></div><div><div><div>142</div></div><div><span><span>      </span></span><span>body.</span><span>safetySettings</span><span> </span><span>=</span><span> extra.</span><span>safety_settings</span><span>;</span></div></div><div><div><div>143</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>144</div></div><div><span>    </span><span>if</span><span> (extra.</span><span>cached_content</span><span>) {</span></div></div><div><div><div>145</div></div><div><span><span>      </span></span><span>body.</span><span>cachedContent</span><span> </span><span>=</span><span> extra.</span><span>cached_content</span><span>;</span></div></div><div><div><div>146</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>147</div></div><div><span>    </span><span>if</span><span> (extra.</span><span>thinking_config</span><span>) {</span></div></div><div><div><div>148</div></div><div><span><span>      </span></span><span>body.</span><span>generationConfig</span><span>.</span><span>thinkingConfig</span><span> </span><span>=</span><span> extra.</span><span>thinking_config</span><span>;</span></div></div><div><div><div>149</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>150</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>151</div></div><div><span>  </span><span>switch</span><span> (</span><span>true</span><span>) {</span></div></div><div><div><div>152</div></div><div><span>    </span><span>case</span><span> model.</span><span>endsWith</span><span>(</span><span>":search"</span><span>):</span></div></div><div><div><div>153</div></div><div><span><span>      </span></span><span>model </span><span>=</span><span> model.</span><span>substring</span><span>(</span><span>0</span><span>, model.</span><span>length</span><span> </span><span>-</span><span> </span><span>7</span><span>);</span></div></div><div><div><div>154</div></div><div><span>      </span><span>// eslint-disable-next-line no-fallthrough</span></div></div><div><div><div>155</div></div><div><span>    </span><span>case</span><span> req.</span><span>model</span><span>.</span><span>endsWith</span><span>(</span><span>"-search-preview"</span><span>):</span></div></div><div><div><div>156</div></div><div><span><span>      </span></span><span>body.</span><span>tools</span><span> </span><span>=</span><span> body.</span><span>tools</span><span> </span><span>||</span><span> [];</span></div></div><div><div><div>157</div></div><div><span><span>      </span></span><span>body.</span><span>tools</span><span>.</span><span>push</span><span>({</span><span>googleSearch</span><span>:</span><span> {}});</span></div></div><div><div><div>158</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>159</div></div><div><span>  </span><span>const</span><span> </span><span>TASK</span><span> </span><span>=</span><span> req.</span><span>stream</span><span> </span><span>?</span><span> </span><span>"streamGenerateContent"</span><span> </span><span>:</span><span> </span><span>"generateContent"</span><span>;</span></div></div><div><div><div>160</div></div><div><span>  </span><span>let</span><span> url </span><span>=</span><span> </span><span>`</span><span>${</span><span>BASE_URL</span><span>}</span><span>/</span><span>${</span><span>API_VERSION</span><span>}</span><span>/models/</span><span>${</span><span>model</span><span>}</span><span>:</span><span>${</span><span>TASK</span><span>}</span><span>`</span><span>;</span></div></div><div><div><div>161</div></div><div><span>  </span><span>if</span><span> (req.</span><span>stream</span><span>) { url </span><span>+=</span><span> </span><span>"?alt=sse"</span><span>; }</span></div></div><div><div><div>162</div></div><div><span>  </span><span>const</span><span> </span><span>response</span><span> </span><span>=</span><span> </span><span>await</span><span> </span><span>fetch</span><span>(url, {</span></div></div><div><div><div>163</div></div><div><span><span>    </span></span><span>method</span><span>:</span><span> </span><span>"POST"</span><span>,</span></div></div><div><div><div>164</div></div><div><span><span>    </span></span><span>headers</span><span>:</span><span> </span><span>makeHeaders</span><span>(apiKey, { </span><span>"Content-Type"</span><span>:</span><span> </span><span>"application/json"</span><span> }),</span></div></div><div><div><div>165</div></div><div><span><span>    </span></span><span>body</span><span>:</span><span> </span><span>JSON</span><span>.</span><span>stringify</span><span>(body),</span></div></div><div><div><div>166</div></div><div><span><span>  </span></span><span>});</span></div></div><div><div><div>167</div></div><div>
</div></div><div><div><div>168</div></div><div><span><span>  </span></span><span>body </span><span>=</span><span> response.</span><span>body</span><span>;</span></div></div><div><div><div>169</div></div><div><span>  </span><span>if</span><span> (response.</span><span>ok</span><span>) {</span></div></div><div><div><div>170</div></div><div><span>    </span><span>let</span><span> id </span><span>=</span><span> </span><span>generateChatcmplId</span><span>();</span></div></div><div><div><div>171</div></div><div><span>    </span><span>if</span><span> (req.</span><span>stream</span><span>) {</span></div></div><div><div><div>172</div></div><div><span><span>      </span></span><span>body </span><span>=</span><span> response.</span><span>body</span></div></div><div><div><div>173</div></div><div><span><span>        </span></span><span>.</span><span>pipeThrough</span><span>(</span><span>new</span><span> </span><span>TextDecoderStream</span><span>())</span></div></div><div><div><div>174</div></div><div><span><span>        </span></span><span>.</span><span>pipeThrough</span><span>(</span><span>new</span><span> </span><span>TransformStream</span><span>({</span></div></div><div><div><div>175</div></div><div><span><span>          </span></span><span>transform</span><span>:</span><span> parseStream,</span></div></div><div><div><div>176</div></div><div><span><span>          </span></span><span>flush</span><span>:</span><span> parseStreamFlush,</span></div></div><div><div><div>177</div></div><div><span><span>          </span></span><span>buffer</span><span>:</span><span> </span><span>""</span><span>,</span></div></div><div><div><div>178</div></div><div><span><span>        </span></span><span>}))</span></div></div><div><div><div>179</div></div><div><span><span>        </span></span><span>.</span><span>pipeThrough</span><span>(</span><span>new</span><span> </span><span>TransformStream</span><span>({</span></div></div><div><div><div>180</div></div><div><span><span>          </span></span><span>transform</span><span>:</span><span> toOpenAiStream,</span></div></div><div><div><div>181</div></div><div><span><span>          </span></span><span>flush</span><span>:</span><span> toOpenAiStreamFlush,</span></div></div><div><div><div>182</div></div><div><span><span>          </span></span><span>streamIncludeUsage</span><span>:</span><span> req.</span><span>stream_options</span><span>?.</span><span>include_usage</span><span>,</span></div></div><div><div><div>183</div></div><div><span><span>          </span></span><span>model, id, </span><span>last</span><span>:</span><span> [],</span></div></div><div><div><div>184</div></div><div><span><span>        </span></span><span>}))</span></div></div><div><div><div>185</div></div><div><span><span>        </span></span><span>.</span><span>pipeThrough</span><span>(</span><span>new</span><span> </span><span>TextEncoderStream</span><span>());</span></div></div><div><div><div>186</div></div><div><span><span>    </span></span><span>} </span><span>else</span><span> {</span></div></div><div><div><div>187</div></div><div><span><span>      </span></span><span>body </span><span>=</span><span> </span><span>await</span><span> response.</span><span>text</span><span>();</span></div></div><div><div><div>188</div></div><div><span>      </span><span>try</span><span> {</span></div></div><div><div><div>189</div></div><div><span><span>         </span></span><span>body </span><span>=</span><span> </span><span>JSON</span><span>.</span><span>parse</span><span>(body);</span></div></div><div><div><div>190</div></div><div><span>         </span><span>if</span><span> (</span><span>!</span><span>body.</span><span>candidates</span><span>) {</span></div></div><div><div><div>191</div></div><div><span>           </span><span>throw</span><span> </span><span>new</span><span> </span><span>Error</span><span>(</span><span>"Invalid completion object"</span><span>);</span></div></div><div><div><div>192</div></div><div><span><span>         </span></span><span>}</span></div></div><div><div><div>193</div></div><div><span><span>       </span></span><span>} </span><span>catch</span><span> (err) {</span></div></div><div><div><div>194</div></div><div><span><span>         </span></span><span>console.</span><span>error</span><span>(</span><span>"Error parsing response:"</span><span>, err);</span></div></div><div><div><div>195</div></div><div><span>         </span><span>return</span><span> </span><span>new</span><span> </span><span>Response</span><span>(body, </span><span>fixCors</span><span>(response)); </span><span>// output as is</span></div></div><div><div><div>196</div></div><div><span><span>       </span></span><span>}</span></div></div><div><div><div>197</div></div><div><span><span>       </span></span><span>body </span><span>=</span><span> </span><span>processCompletionsResponse</span><span>(body, model, id);</span></div></div><div><div><div>198</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>199</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>200</div></div><div><span>  </span><span>return</span><span> </span><span>new</span><span> </span><span>Response</span><span>(body, </span><span>fixCors</span><span>(response));</span></div></div><div><div><div>201</div></div><div><span>}</span></div></div><div><div><div>202</div></div><div>
</div></div><div><div><div>203</div></div><div><span>const</span><span> </span><span>adjustProps</span><span> </span><span>=</span><span><span> (</span><span>schemaPart</span><span>) </span></span><span>=&gt;</span><span> {</span></div></div><div><div><div>204</div></div><div><span>   </span><span>if</span><span> (</span><span>typeof</span><span> schemaPart </span><span>!==</span><span> </span><span>"object"</span><span> </span><span>||</span><span> schemaPart </span><span>===</span><span> </span><span>null</span><span>) {</span></div></div><div><div><div>205</div></div><div><span>     </span><span>return</span><span>;</span></div></div><div><div><div>206</div></div><div><span><span>   </span></span><span>}</span></div></div><div><div><div>207</div></div><div><span>   </span><span>if</span><span> (Array.</span><span>isArray</span><span>(schemaPart)) {</span></div></div><div><div><div>208</div></div><div><span><span>     </span></span><span>schemaPart.</span><span>forEach</span><span>(adjustProps);</span></div></div><div><div><div>209</div></div><div><span><span>   </span></span><span>} </span><span>else</span><span> {</span></div></div><div><div><div>210</div></div><div><span>     </span><span>if</span><span> (schemaPart.</span><span>type</span><span> </span><span>===</span><span> </span><span>"object"</span><span> </span><span>&amp;&amp;</span><span> schemaPart.</span><span>properties</span><span> </span><span>&amp;&amp;</span><span> schemaPart.</span><span>additionalProperties</span><span> </span><span>===</span><span> </span><span>false</span><span>) {</span></div></div><div><div><div>211</div></div><div><span>       </span><span>delete</span><span> schemaPart.</span><span>additionalProperties</span><span>;</span></div></div><div><div><div>212</div></div><div><span><span>     </span></span><span>}</span></div></div><div><div><div>213</div></div><div><span><span>     </span></span><span>Object.</span><span>values</span><span>(schemaPart).</span><span>forEach</span><span>(adjustProps);</span></div></div><div><div><div>214</div></div><div><span><span>   </span></span><span>}</span></div></div><div><div><div>215</div></div><div><span><span> </span></span><span>};</span></div></div><div><div><div>216</div></div><div><span> </span><span>const</span><span> </span><span>adjustSchema</span><span> </span><span>=</span><span><span> (</span><span>schema</span><span>) </span></span><span>=&gt;</span><span> {</span></div></div><div><div><div>217</div></div><div><span>   </span><span>const</span><span> </span><span>obj</span><span> </span><span>=</span><span> schema[schema.</span><span>type</span><span>];</span></div></div><div><div><div>218</div></div><div><span>   </span><span>delete</span><span> obj.</span><span>strict</span><span>;</span></div></div><div><div><div>219</div></div><div><span>   </span><span>return</span><span> </span><span>adjustProps</span><span>(schema);</span></div></div><div><div><div>220</div></div><div><span><span> </span></span><span>};</span></div></div><div><div><div>221</div></div><div>
</div></div><div><div><div>222</div></div><div><span>const</span><span> </span><span>harmCategory</span><span> </span><span>=</span><span> [</span></div></div><div><div><div>223</div></div><div><span>  </span><span>"HARM_CATEGORY_HATE_SPEECH"</span><span>,</span></div></div><div><div><div>224</div></div><div><span>  </span><span>"HARM_CATEGORY_SEXUALLY_EXPLICIT"</span><span>,</span></div></div><div><div><div>225</div></div><div><span>  </span><span>"HARM_CATEGORY_DANGEROUS_CONTENT"</span><span>,</span></div></div><div><div><div>226</div></div><div><span>  </span><span>"HARM_CATEGORY_HARASSMENT"</span><span>,</span></div></div><div><div><div>227</div></div><div><span>  </span><span>"HARM_CATEGORY_CIVIC_INTEGRITY"</span><span>,</span></div></div><div><div><div>228</div></div><div><span>];</span></div></div><div><div><div>229</div></div><div>
</div></div><div><div><div>230</div></div><div><span>const</span><span> </span><span>safetySettings</span><span> </span><span>=</span><span> harmCategory.</span><span>map</span><span><span>(</span><span>category</span><span> </span></span><span>=&gt;</span><span> ({</span></div></div><div><div><div>231</div></div><div><span><span>  </span></span><span>category,</span></div></div><div><div><div>232</div></div><div><span><span>  </span></span><span>threshold</span><span>:</span><span> </span><span>"BLOCK_NONE"</span><span>,</span></div></div><div><div><div>233</div></div><div><span>}));</span></div></div><div><div><div>234</div></div><div>
</div></div><div><div><div>235</div></div><div><span>const</span><span> </span><span>fieldsMap</span><span> </span><span>=</span><span> {</span></div></div><div><div><div>236</div></div><div><span><span>  </span></span><span>frequency_penalty</span><span>:</span><span> </span><span>"frequencyPenalty"</span><span>,</span></div></div><div><div><div>237</div></div><div><span><span>  </span></span><span>max_completion_tokens</span><span>:</span><span> </span><span>"maxOutputTokens"</span><span>,</span></div></div><div><div><div>238</div></div><div><span><span>  </span></span><span>max_tokens</span><span>:</span><span> </span><span>"maxOutputTokens"</span><span>,</span></div></div><div><div><div>239</div></div><div><span><span>  </span></span><span>n</span><span>:</span><span> </span><span>"candidateCount"</span><span>, </span><span>// not for streaming</span></div></div><div><div><div>240</div></div><div><span><span>  </span></span><span>presence_penalty</span><span>:</span><span> </span><span>"presencePenalty"</span><span>,</span></div></div><div><div><div>241</div></div><div><span><span>  </span></span><span>seed</span><span>:</span><span> </span><span>"seed"</span><span>,</span></div></div><div><div><div>242</div></div><div><span><span>  </span></span><span>stop</span><span>:</span><span> </span><span>"stopSequences"</span><span>,</span></div></div><div><div><div>243</div></div><div><span><span>  </span></span><span>temperature</span><span>:</span><span> </span><span>"temperature"</span><span>,</span></div></div><div><div><div>244</div></div><div><span><span>  </span></span><span>top_k</span><span>:</span><span> </span><span>"topK"</span><span>, </span><span>// non-standard</span></div></div><div><div><div>245</div></div><div><span><span>  </span></span><span>top_p</span><span>:</span><span> </span><span>"topP"</span><span>,</span></div></div><div><div><div>246</div></div><div><span>};</span></div></div><div><div><div>247</div></div><div>
</div></div><div><div><div>248</div></div><div><span>const</span><span> </span><span>thinkingBudgetMap</span><span> </span><span>=</span><span> {</span></div></div><div><div><div>249</div></div><div><span><span>  </span></span><span>low</span><span>:</span><span> </span><span>1024</span><span>,</span></div></div><div><div><div>250</div></div><div><span><span>  </span></span><span>medium</span><span>:</span><span> </span><span>8192</span><span>,</span></div></div><div><div><div>251</div></div><div><span><span>  </span></span><span>high</span><span>:</span><span> </span><span>24576</span><span>,</span></div></div><div><div><div>252</div></div><div><span>};</span></div></div><div><div><div>253</div></div><div>
</div></div><div><div><div>254</div></div><div><span>const</span><span> </span><span>transformConfig</span><span> </span><span>=</span><span><span> (</span><span>req</span><span>) </span></span><span>=&gt;</span><span> {</span></div></div><div><div><div>255</div></div><div><span>  </span><span>let</span><span> cfg </span><span>=</span><span> {};</span></div></div><div><div><div>256</div></div><div><span>  </span><span>for</span><span> (</span><span>let</span><span> key </span><span>in</span><span> req) {</span></div></div><div><div><div>257</div></div><div><span>    </span><span>const</span><span> </span><span>matchedKey</span><span> </span><span>=</span><span> fieldsMap[key];</span></div></div><div><div><div>258</div></div><div><span>    </span><span>if</span><span> (matchedKey) {</span></div></div><div><div><div>259</div></div><div><span><span>      </span></span><span>cfg[matchedKey] </span><span>=</span><span> req[key];</span></div></div><div><div><div>260</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>261</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>262</div></div><div><span>  </span><span>if</span><span> (req.</span><span>response_format</span><span>) {</span></div></div><div><div><div>263</div></div><div><span>    </span><span>switch</span><span> (req.</span><span>response_format</span><span>.</span><span>type</span><span>) {</span></div></div><div><div><div>264</div></div><div><span>      </span><span>case</span><span> </span><span>"json_schema"</span><span>:</span></div></div><div><div><div>265</div></div><div><span>        </span><span>adjustSchema</span><span>(req.</span><span>response_format</span><span>);</span></div></div><div><div><div>266</div></div><div><span><span>        </span></span><span>cfg.</span><span>responseSchema</span><span> </span><span>=</span><span> req.</span><span>response_format</span><span>.</span><span>json_schema</span><span>?.</span><span>schema</span><span>;</span></div></div><div><div><div>267</div></div><div><span>        </span><span>if</span><span> (cfg.</span><span>responseSchema</span><span> </span><span>&amp;&amp;</span><span> </span><span>"enum"</span><span> </span><span>in</span><span> cfg.</span><span>responseSchema</span><span>) {</span></div></div><div><div><div>268</div></div><div><span><span>          </span></span><span>cfg.</span><span>responseMimeType</span><span> </span><span>=</span><span> </span><span>"text/x.enum"</span><span>;</span></div></div><div><div><div>269</div></div><div><span>          </span><span>break</span><span>;</span></div></div><div><div><div>270</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>271</div></div><div><span>      </span><span>case</span><span> </span><span>"json_object"</span><span>:</span></div></div><div><div><div>272</div></div><div><span><span>        </span></span><span>cfg.</span><span>responseMimeType</span><span> </span><span>=</span><span> </span><span>"application/json"</span><span>;</span></div></div><div><div><div>273</div></div><div><span>        </span><span>break</span><span>;</span></div></div><div><div><div>274</div></div><div><span>      </span><span>case</span><span> </span><span>"text"</span><span>:</span></div></div><div><div><div>275</div></div><div><span><span>        </span></span><span>cfg.</span><span>responseMimeType</span><span> </span><span>=</span><span> </span><span>"text/plain"</span><span>;</span></div></div><div><div><div>276</div></div><div><span>        </span><span>break</span><span>;</span></div></div><div><div><div>277</div></div><div><span>      </span><span>default</span><span>:</span></div></div><div><div><div>278</div></div><div><span>        </span><span>throw</span><span> </span><span>new</span><span> </span><span>HttpError</span><span>(</span><span>"Unsupported response_format.type"</span><span>, </span><span>400</span><span>);</span></div></div><div><div><div>279</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>280</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>281</div></div><div><span>  </span><span>if</span><span> (req.</span><span>reasoning_effort</span><span>) {</span></div></div><div><div><div>282</div></div><div><span><span>    </span></span><span>cfg.</span><span>thinkingConfig</span><span> </span><span>=</span><span> { </span><span>thinkingBudget</span><span>:</span><span> thinkingBudgetMap[req.</span><span>reasoning_effort</span><span>] };</span></div></div><div><div><div>283</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>284</div></div><div><span>  </span><span>return</span><span> cfg;</span></div></div><div><div><div>285</div></div><div><span>};</span></div></div><div><div><div>286</div></div><div>
</div></div><div><div><div>287</div></div><div><span>const</span><span> </span><span>parseImg</span><span> </span><span>=</span><span> </span><span>async</span><span><span> (</span><span>url</span><span>) </span></span><span>=&gt;</span><span> {</span></div></div><div><div><div>288</div></div><div><span>  </span><span>let</span><span> mimeType, data;</span></div></div><div><div><div>289</div></div><div><span>  </span><span>if</span><span> (url.</span><span>startsWith</span><span>(</span><span>"http://"</span><span>) </span><span>||</span><span> url.</span><span>startsWith</span><span>(</span><span>"https://"</span><span>)) {</span></div></div><div><div><div>290</div></div><div><span>    </span><span>try</span><span> {</span></div></div><div><div><div>291</div></div><div><span>      </span><span>const</span><span> </span><span>response</span><span> </span><span>=</span><span> </span><span>await</span><span> </span><span>fetch</span><span>(url);</span></div></div><div><div><div>292</div></div><div><span>      </span><span>if</span><span> (</span><span>!</span><span>response.</span><span>ok</span><span>) {</span></div></div><div><div><div>293</div></div><div><span>        </span><span>throw</span><span> </span><span>new</span><span> </span><span>Error</span><span>(</span><span>`</span><span>${</span><span>response</span><span>.</span><span>status</span><span>}</span><span> </span><span>${</span><span>response</span><span>.</span><span>statusText</span><span>}</span><span> (</span><span>${</span><span>url</span><span>}</span><span>)`</span><span>);</span></div></div><div><div><div>294</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>295</div></div><div><span><span>      </span></span><span>mimeType </span><span>=</span><span> response.</span><span>headers</span><span>.</span><span>get</span><span>(</span><span>"content-type"</span><span>);</span></div></div><div><div><div>296</div></div><div><span><span>      </span></span><span>data </span><span>=</span><span> Buffer.</span><span>from</span><span>(</span><span>await</span><span> response.</span><span>arrayBuffer</span><span>()).</span><span>toString</span><span>(</span><span>"base64"</span><span>);</span></div></div><div><div><div>297</div></div><div><span><span>    </span></span><span>} </span><span>catch</span><span> (err) {</span></div></div><div><div><div>298</div></div><div><span>      </span><span>throw</span><span> </span><span>new</span><span> </span><span>HttpError</span><span>(</span><span>"Error fetching image: "</span><span> </span><span>+</span><span> err.</span><span>toString</span><span>(), </span><span>400</span><span>);</span></div></div><div><div><div>299</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>300</div></div><div><span><span>  </span></span><span>} </span><span>else</span><span> {</span></div></div><div><div><div>301</div></div><div><span>    </span><span>const</span><span> </span><span>match</span><span> </span><span>=</span><span> url.</span><span>match</span><span>(</span><span>/</span><span>^</span><span>data:(?&lt;</span><span>mimeType</span><span>&gt;</span><span>.</span><span><span>*?</span><span>)(;base64)</span><span>?</span><span>,(?&lt;</span></span><span>data</span><span>&gt;</span><span>.</span><span><span>*</span><span>)</span></span><span>$</span><span>/</span><span>);</span></div></div><div><div><div>302</div></div><div><span>    </span><span>if</span><span> (</span><span>!</span><span>match) {</span></div></div><div><div><div>303</div></div><div><span>      </span><span>throw</span><span> </span><span>new</span><span> </span><span>HttpError</span><span>(</span><span>"Invalid image data: "</span><span> </span><span>+</span><span> url, </span><span>400</span><span>);</span></div></div><div><div><div>304</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>305</div></div><div><span><span>    </span></span><span>({ mimeType, data } </span><span>=</span><span> match.</span><span>groups</span><span>);</span></div></div><div><div><div>306</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>307</div></div><div><span>  </span><span>return</span><span> {</span></div></div><div><div><div>308</div></div><div><span><span>    </span></span><span>inlineData</span><span>:</span><span> {</span></div></div><div><div><div>309</div></div><div><span><span>      </span></span><span>mimeType,</span></div></div><div><div><div>310</div></div><div><span><span>      </span></span><span>data,</span></div></div><div><div><div>311</div></div><div><span><span>    </span></span><span>},</span></div></div><div><div><div>312</div></div><div><span><span>  </span></span><span>};</span></div></div><div><div><div>313</div></div><div><span>};</span></div></div><div><div><div>314</div></div><div>
</div></div><div><div><div>315</div></div><div><span>const</span><span> </span><span>transformMsg</span><span> </span><span>=</span><span> </span><span>async</span><span><span> ({ </span><span>content</span><span>, </span><span>tool_calls</span><span>, </span><span>tool_call_id</span><span> }, </span><span>fnames</span><span>) </span></span><span>=&gt;</span><span> {</span></div></div><div><div><div>316</div></div><div><span>  </span><span>const</span><span> </span><span>parts</span><span> </span><span>=</span><span> [];</span></div></div><div><div><div>317</div></div><div><span>  </span><span>if</span><span> (tool_call_id </span><span>!==</span><span> </span><span>undefined</span><span>) {</span></div></div><div><div><div>318</div></div><div><span>    </span><span>let</span><span> response;</span></div></div><div><div><div>319</div></div><div><span>    </span><span>try</span><span> {</span></div></div><div><div><div>320</div></div><div><span><span>      </span></span><span>response </span><span>=</span><span> </span><span>JSON</span><span>.</span><span>parse</span><span>(content);</span></div></div><div><div><div>321</div></div><div><span><span>    </span></span><span>} </span><span>catch</span><span> (err) {</span></div></div><div><div><div>322</div></div><div><span><span>      </span></span><span>console.</span><span>error</span><span>(</span><span>"Error parsing function response content:"</span><span>, err);</span></div></div><div><div><div>323</div></div><div><span>      </span><span>throw</span><span> </span><span>new</span><span> </span><span>HttpError</span><span>(</span><span>"Invalid function response: "</span><span> </span><span>+</span><span> content, </span><span>400</span><span>);</span></div></div><div><div><div>324</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>325</div></div><div><span>    </span><span>if</span><span> (</span><span>typeof</span><span> response </span><span>!==</span><span> </span><span>"object"</span><span> </span><span>||</span><span> response </span><span>===</span><span> </span><span>null</span><span> </span><span>||</span><span> Array.</span><span>isArray</span><span>(response)) {</span></div></div><div><div><div>326</div></div><div><span><span>      </span></span><span>response </span><span>=</span><span> { </span><span>result</span><span>:</span><span> response };</span></div></div><div><div><div>327</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>328</div></div><div><span><span>    </span></span><span>parts.</span><span>push</span><span>({</span></div></div><div><div><div>329</div></div><div><span><span>      </span></span><span>functionResponse</span><span>:</span><span> {</span></div></div><div><div><div>330</div></div><div><span><span>        </span></span><span>id</span><span>:</span><span> tool_call_id.</span><span>startsWith</span><span>(</span><span>"{"</span><span>) </span><span>?</span><span> </span><span>null</span><span> </span><span>:</span><span> tool_call_id,</span></div></div><div><div><div>331</div></div><div><span><span>        </span></span><span>name</span><span>:</span><span> fnames[tool_call_id],</span></div></div><div><div><div>332</div></div><div><span><span>        </span></span><span>response,</span></div></div><div><div><div>333</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>334</div></div><div><span><span>    </span></span><span>});</span></div></div><div><div><div>335</div></div><div><span>    </span><span>return</span><span> parts;</span></div></div><div><div><div>336</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>337</div></div><div><span>  </span><span>if</span><span> (tool_calls) {</span></div></div><div><div><div>338</div></div><div><span>    </span><span>for</span><span> (</span><span>const</span><span> </span><span>tcall</span><span> </span><span>of</span><span> tool_calls) {</span></div></div><div><div><div>339</div></div><div><span>      </span><span>if</span><span> (tcall.</span><span>type</span><span> </span><span>!==</span><span> </span><span>"function"</span><span>) {</span></div></div><div><div><div>340</div></div><div><span>        </span><span>throw</span><span> </span><span>new</span><span> </span><span>HttpError</span><span>(</span><span>`Unsupported tool_call type: "</span><span>${</span><span>tcall</span><span>.</span><span>type</span><span>}</span><span>"`</span><span>, </span><span>400</span><span>);</span></div></div><div><div><div>341</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>342</div></div><div><span>      </span><span>const</span><span> { </span><span>function</span><span>: { </span><span>arguments</span><span>: </span><span>argstr</span><span>, </span><span>name</span><span> }, </span><span>id</span><span> } </span><span>=</span><span> tcall;</span></div></div><div><div><div>343</div></div><div><span>      </span><span>let</span><span> args;</span></div></div><div><div><div>344</div></div><div><span>      </span><span>try</span><span> {</span></div></div><div><div><div>345</div></div><div><span><span>        </span></span><span>args </span><span>=</span><span> </span><span>JSON</span><span>.</span><span>parse</span><span>(argstr);</span></div></div><div><div><div>346</div></div><div><span><span>      </span></span><span>} </span><span>catch</span><span> (err) {</span></div></div><div><div><div>347</div></div><div><span><span>        </span></span><span>console.</span><span>error</span><span>(</span><span>"Error parsing function arguments:"</span><span>, err);</span></div></div><div><div><div>348</div></div><div><span>        </span><span>throw</span><span> </span><span>new</span><span> </span><span>HttpError</span><span>(</span><span>"Invalid function arguments: "</span><span> </span><span>+</span><span> argstr, </span><span>400</span><span>);</span></div></div><div><div><div>349</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>350</div></div><div><span><span>      </span></span><span>parts.</span><span>push</span><span>({</span></div></div><div><div><div>351</div></div><div><span><span>        </span></span><span>functionCall</span><span>:</span><span> {</span></div></div><div><div><div>352</div></div><div><span><span>          </span></span><span>id</span><span>:</span><span> id.</span><span>startsWith</span><span>(</span><span>"{"</span><span>) </span><span>?</span><span> </span><span>null</span><span> </span><span>:</span><span> id,</span></div></div><div><div><div>353</div></div><div><span><span>          </span></span><span>name,</span></div></div><div><div><div>354</div></div><div><span><span>          </span></span><span>args,</span></div></div><div><div><div>355</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>356</div></div><div><span><span>      </span></span><span>});</span></div></div><div><div><div>357</div></div><div><span><span>      </span></span><span>fnames[id] </span><span>=</span><span> name;</span></div></div><div><div><div>358</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>359</div></div><div><span>    </span><span>return</span><span> parts;</span></div></div><div><div><div>360</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>361</div></div><div><span>  </span><span>if</span><span> (</span><span>!</span><span>Array.</span><span>isArray</span><span>(content)) {</span></div></div><div><div><div>362</div></div><div><span><span>    </span></span><span>parts.</span><span>push</span><span>({ </span><span>text</span><span>:</span><span> content });</span></div></div><div><div><div>363</div></div><div><span>    </span><span>return</span><span> parts;</span></div></div><div><div><div>364</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>365</div></div><div><span>  </span><span>for</span><span> (</span><span>const</span><span> </span><span>item</span><span> </span><span>of</span><span> content) {</span></div></div><div><div><div>366</div></div><div><span>    </span><span>switch</span><span> (item.</span><span>type</span><span>) {</span></div></div><div><div><div>367</div></div><div><span>      </span><span>case</span><span> </span><span>"text"</span><span>:</span></div></div><div><div><div>368</div></div><div><span><span>        </span></span><span>parts.</span><span>push</span><span>({ </span><span>text</span><span>:</span><span> item.</span><span>text</span><span> });</span></div></div><div><div><div>369</div></div><div><span>        </span><span>break</span><span>;</span></div></div><div><div><div>370</div></div><div><span>      </span><span>case</span><span> </span><span>"image_url"</span><span>:</span></div></div><div><div><div>371</div></div><div><span><span>        </span></span><span>parts.</span><span>push</span><span>(</span><span>await</span><span> </span><span>parseImg</span><span>(item.</span><span>image_url</span><span>.</span><span>url</span><span>));</span></div></div><div><div><div>372</div></div><div><span>        </span><span>break</span><span>;</span></div></div><div><div><div>373</div></div><div><span>      </span><span>case</span><span> </span><span>"input_audio"</span><span>:</span></div></div><div><div><div>374</div></div><div><span><span>        </span></span><span>parts.</span><span>push</span><span>({</span></div></div><div><div><div>375</div></div><div><span><span>          </span></span><span>inlineData</span><span>:</span><span> {</span></div></div><div><div><div>376</div></div><div><span><span>            </span></span><span>mimeType</span><span>:</span><span> </span><span>"audio/"</span><span> </span><span>+</span><span> item.</span><span>input_audio</span><span>.</span><span>format</span><span>,</span></div></div><div><div><div>377</div></div><div><span><span>            </span></span><span>data</span><span>:</span><span> item.</span><span>input_audio</span><span>.</span><span>data</span><span>,</span></div></div><div><div><div>378</div></div><div><span><span>          </span></span><span>}</span></div></div><div><div><div>379</div></div><div><span><span>        </span></span><span>});</span></div></div><div><div><div>380</div></div><div><span>        </span><span>break</span><span>;</span></div></div><div><div><div>381</div></div><div><span>      </span><span>default</span><span>:</span></div></div><div><div><div>382</div></div><div><span>        </span><span>throw</span><span> </span><span>new</span><span> </span><span>HttpError</span><span>(</span><span>`Unknown "content" item type: "</span><span>${</span><span>item</span><span>.</span><span>type</span><span>}</span><span>"`</span><span>, </span><span>400</span><span>);</span></div></div><div><div><div>383</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>384</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>385</div></div><div><span>  </span><span>if</span><span> (content.</span><span>every</span><span><span>(</span><span>item</span><span> </span></span><span>=&gt;</span><span> item.</span><span>type</span><span> </span><span>===</span><span> </span><span>"image_url"</span><span>)) {</span></div></div><div><div><div>386</div></div><div><span><span>    </span></span><span>parts.</span><span>push</span><span>({ </span><span>text</span><span>:</span><span> </span><span>""</span><span> }); </span><span>// to avoid "Unable to submit request because it must have a text parameter"</span></div></div><div><div><div>387</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>388</div></div><div><span>  </span><span>return</span><span> parts;</span></div></div><div><div><div>389</div></div><div><span>};</span></div></div><div><div><div>390</div></div><div>
</div></div><div><div><div>391</div></div><div><span>const</span><span> </span><span>transformMessages</span><span> </span><span>=</span><span> </span><span>async</span><span><span> (</span><span>messages</span><span>) </span></span><span>=&gt;</span><span> {</span></div></div><div><div><div>392</div></div><div><span>  </span><span>if</span><span> (</span><span>!</span><span>messages) { </span><span>return</span><span>; }</span></div></div><div><div><div>393</div></div><div><span>  </span><span>const</span><span> </span><span>contents</span><span> </span><span>=</span><span> [];</span></div></div><div><div><div>394</div></div><div><span>  </span><span>let</span><span> system_instruction;</span></div></div><div><div><div>395</div></div><div><span>  </span><span>const</span><span> </span><span>fnames</span><span> </span><span>=</span><span> {}; </span><span>// cache function names by tool_call_id between messages</span></div></div><div><div><div>396</div></div><div><span>  </span><span>for</span><span> (</span><span>const</span><span> </span><span>item</span><span> </span><span>of</span><span> messages) {</span></div></div><div><div><div>397</div></div><div><span>    </span><span>if</span><span> (item.</span><span>role</span><span> </span><span>===</span><span> </span><span>"system"</span><span>) {</span></div></div><div><div><div>398</div></div><div><span>      </span><span>delete</span><span> item.</span><span>role</span><span>;</span></div></div><div><div><div>399</div></div><div><span><span>      </span></span><span>system_instruction </span><span>=</span><span> { </span><span>parts</span><span>:</span><span> </span><span>await</span><span> </span><span>transformMsg</span><span>(item) };</span></div></div><div><div><div>400</div></div><div><span><span>    </span></span><span>} </span><span>else</span><span> {</span></div></div><div><div><div>401</div></div><div><span>      </span><span>if</span><span> (item.</span><span>role</span><span> </span><span>===</span><span> </span><span>"assistant"</span><span>) {</span></div></div><div><div><div>402</div></div><div><span><span>        </span></span><span>item.</span><span>role</span><span> </span><span>=</span><span> </span><span>"model"</span><span>;</span></div></div><div><div><div>403</div></div><div><span><span>      </span></span><span>} </span><span>else</span><span> </span><span>if</span><span> (item.</span><span>role</span><span> </span><span>===</span><span> </span><span>"tool"</span><span>) {</span></div></div><div><div><div>404</div></div><div><span>      </span><span>const</span><span> </span><span>prev</span><span> </span><span>=</span><span> contents[contents.</span><span>length</span><span> </span><span>-</span><span> </span><span>1</span><span>];</span></div></div><div><div><div>405</div></div><div><span>      </span><span>if</span><span> (prev?.</span><span>role</span><span> </span><span>===</span><span> </span><span>"function"</span><span>) {</span></div></div><div><div><div>406</div></div><div><span><span>        </span></span><span>prev.</span><span>parts</span><span>.</span><span>push</span><span>(</span><span>...</span><span>await</span><span> </span><span>transformMsg</span><span>(item, fnames));</span></div></div><div><div><div>407</div></div><div><span>        </span><span>continue</span><span>;</span></div></div><div><div><div>408</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>409</div></div><div><span><span>      </span></span><span>item.</span><span>role</span><span> </span><span>=</span><span> </span><span>"function"</span><span>; </span><span>// ignored</span></div></div><div><div><div>410</div></div><div><span><span>      </span></span><span>} </span><span>else</span><span> </span><span>if</span><span> (item.</span><span>role</span><span> </span><span>!==</span><span> </span><span>"user"</span><span>) {</span></div></div><div><div><div>411</div></div><div><span>        </span><span>throw</span><span> </span><span>HttpError</span><span>(</span><span>`Unknown message role: "</span><span>${</span><span>item</span><span>.</span><span>role</span><span>}</span><span>"`</span><span>, </span><span>400</span><span>);</span></div></div><div><div><div>412</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>413</div></div><div><span><span>      </span></span><span>contents.</span><span>push</span><span>({</span></div></div><div><div><div>414</div></div><div><span><span>        </span></span><span>role</span><span>:</span><span> item.</span><span>role</span><span>,</span></div></div><div><div><div>415</div></div><div><span><span>        </span></span><span>parts</span><span>:</span><span> </span><span>await</span><span> </span><span>transformMsg</span><span>(item, fnames)</span></div></div><div><div><div>416</div></div><div><span><span>      </span></span><span>});</span></div></div><div><div><div>417</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>418</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>419</div></div><div><span>  </span><span>if</span><span> (system_instruction) {</span></div></div><div><div><div>420</div></div><div><span>    </span><span>if</span><span> (</span><span>!</span><span>contents[</span><span>0</span><span>]?.</span><span>parts</span><span>.</span><span>some</span><span><span>(</span><span>part</span><span> </span></span><span>=&gt;</span><span> part.</span><span>text</span><span>)) {</span></div></div><div><div><div>421</div></div><div><span><span>      </span></span><span>contents.</span><span>unshift</span><span>({ </span><span>role</span><span>:</span><span> </span><span>"user"</span><span>, </span><span>parts</span><span>:</span><span> { </span><span>text</span><span>:</span><span> </span><span>" "</span><span> } });</span></div></div><div><div><div>422</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>423</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>424</div></div><div><span>  </span><span>return</span><span> { system_instruction, contents };</span></div></div><div><div><div>425</div></div><div><span>};</span></div></div><div><div><div>426</div></div><div>
</div></div><div><div><div>427</div></div><div><span>const</span><span> </span><span>transformTools</span><span> </span><span>=</span><span><span> (</span><span>req</span><span>) </span></span><span>=&gt;</span><span> {</span></div></div><div><div><div>428</div></div><div><span>   </span><span>let</span><span> tools, tool_config;</span></div></div><div><div><div>429</div></div><div><span>   </span><span>if</span><span> (req.</span><span>tools</span><span>) {</span></div></div><div><div><div>430</div></div><div><span>     </span><span>const</span><span> </span><span>funcs</span><span> </span><span>=</span><span> req.</span><span>tools</span><span>.</span><span>filter</span><span><span>(</span><span>tool</span><span> </span></span><span>=&gt;</span><span> tool.</span><span>type</span><span> </span><span>===</span><span> </span><span>"function"</span><span>);</span></div></div><div><div><div>431</div></div><div><span><span>     </span></span><span>funcs.</span><span>forEach</span><span>(adjustSchema);</span></div></div><div><div><div>432</div></div><div><span><span>     </span></span><span>tools </span><span>=</span><span> [{ </span><span>function_declarations</span><span>:</span><span> funcs.</span><span>map</span><span><span>(</span><span>schema</span><span> </span></span><span>=&gt;</span><span> schema.</span><span>function</span><span>) }];</span></div></div><div><div><div>433</div></div><div><span><span>   </span></span><span>}</span></div></div><div><div><div>434</div></div><div><span>   </span><span>if</span><span> (req.</span><span>tool_choice</span><span>) {</span></div></div><div><div><div>435</div></div><div><span>     </span><span>const</span><span> </span><span>allowed_function_names</span><span> </span><span>=</span><span> req.</span><span>tool_choice</span><span>?.</span><span>type</span><span> </span><span>===</span><span> </span><span>"function"</span><span> </span><span>?</span><span> [ req.</span><span>tool_choice</span><span>?.</span><span>function</span><span>?.</span><span>name</span><span> ] </span><span>:</span><span> </span><span>undefined</span><span>;</span></div></div><div><div><div>436</div></div><div><span>     </span><span>if</span><span> (allowed_function_names </span><span>||</span><span> </span><span>typeof</span><span> req.</span><span>tool_choice</span><span> </span><span>===</span><span> </span><span>"string"</span><span>) {</span></div></div><div><div><div>437</div></div><div><span><span>       </span></span><span>tool_config </span><span>=</span><span> {</span></div></div><div><div><div>438</div></div><div><span><span>         </span></span><span>function_calling_config</span><span>:</span><span> {</span></div></div><div><div><div>439</div></div><div><span><span>           </span></span><span>mode</span><span>:</span><span> allowed_function_names </span><span>?</span><span> </span><span>"ANY"</span><span> </span><span>:</span><span> req.</span><span>tool_choice</span><span>.</span><span>toUpperCase</span><span>(),</span></div></div><div><div><div>440</div></div><div><span><span>           </span></span><span>allowed_function_names</span></div></div><div><div><div>441</div></div><div><span><span>         </span></span><span>}</span></div></div><div><div><div>442</div></div><div><span><span>       </span></span><span>};</span></div></div><div><div><div>443</div></div><div><span><span>     </span></span><span>}</span></div></div><div><div><div>444</div></div><div><span><span>   </span></span><span>}</span></div></div><div><div><div>445</div></div><div><span>   </span><span>return</span><span> { tools, tool_config };</span></div></div><div><div><div>446</div></div><div><span>};</span></div></div><div><div><div>447</div></div><div>
</div></div><div><div><div>448</div></div><div><span>const</span><span> </span><span>transformRequest</span><span> </span><span>=</span><span> </span><span>async</span><span><span> (</span><span>req</span><span>) </span></span><span>=&gt;</span><span> ({</span></div></div><div><div><div>449</div></div><div><span>  </span><span>...</span><span>await</span><span> </span><span>transformMessages</span><span>(req.</span><span>messages</span><span>),</span></div></div><div><div><div>450</div></div><div><span><span>  </span></span><span>safetySettings,</span></div></div><div><div><div>451</div></div><div><span><span>  </span></span><span>generationConfig</span><span>:</span><span> </span><span>transformConfig</span><span>(req),</span></div></div><div><div><div>452</div></div><div><span>  </span><span>...</span><span>transformTools</span><span>(req),</span></div></div><div><div><div>453</div></div><div><span>});</span></div></div><div><div><div>454</div></div><div>
</div></div><div><div><div>455</div></div><div><span>const</span><span> </span><span>generateChatcmplId</span><span> </span><span>=</span><span> () </span><span>=&gt;</span><span> {</span></div></div><div><div><div>456</div></div><div><span>  </span><span>const</span><span> </span><span>characters</span><span> </span><span>=</span><span> </span><span>"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"</span><span>;</span></div></div><div><div><div>457</div></div><div><span>  </span><span>const</span><span> </span><span>randomChar</span><span> </span><span>=</span><span> () </span><span>=&gt;</span><span> characters[Math.</span><span>floor</span><span>(Math.</span><span>random</span><span>() </span><span>*</span><span> characters.</span><span>length</span><span>)];</span></div></div><div><div><div>458</div></div><div><span>  </span><span>return</span><span> </span><span>"chatcmpl-"</span><span> </span><span>+</span><span> Array.</span><span>from</span><span>({ </span><span>length</span><span>:</span><span> </span><span>29</span><span> }, randomChar).</span><span>join</span><span>(</span><span>""</span><span>);</span></div></div><div><div><div>459</div></div><div><span>};</span></div></div><div><div><div>460</div></div><div>
</div></div><div><div><div>461</div></div><div><span>const</span><span> </span><span>reasonsMap</span><span> </span><span>=</span><span> {</span></div></div><div><div><div>462</div></div><div><span>  </span><span>"STOP"</span><span>:</span><span> </span><span>"stop"</span><span>,</span></div></div><div><div><div>463</div></div><div><span>  </span><span>"MAX_TOKENS"</span><span>:</span><span> </span><span>"length"</span><span>,</span></div></div><div><div><div>464</div></div><div><span>  </span><span>"SAFETY"</span><span>:</span><span> </span><span>"content_filter"</span><span>,</span></div></div><div><div><div>465</div></div><div><span>  </span><span>"RECITATION"</span><span>:</span><span> </span><span>"content_filter"</span><span>,</span></div></div><div><div><div>466</div></div><div><span>};</span></div></div><div><div><div>467</div></div><div><span>const</span><span> </span><span>SEP</span><span> </span><span>=</span><span> </span><span>"</span><span>\n\n</span><span>|&gt;"</span><span>;</span></div></div><div><div><div>468</div></div><div><span>const</span><span> </span><span>transformCandidates</span><span> </span><span>=</span><span><span> (</span><span>key</span><span>, </span><span>cand</span><span>) </span></span><span>=&gt;</span><span> {</span></div></div><div><div><div>469</div></div><div><span>    </span><span>const</span><span> </span><span>message</span><span> </span><span>=</span><span> { </span><span>role</span><span>:</span><span> </span><span>"assistant"</span><span>, </span><span>content</span><span>:</span><span> [] };</span></div></div><div><div><div>470</div></div><div><span>    </span><span>for</span><span> (</span><span>const</span><span> </span><span>part</span><span> </span><span>of</span><span> cand.</span><span>content</span><span>?.</span><span>parts</span><span> </span><span>??</span><span> []) {</span></div></div><div><div><div>471</div></div><div><span>      </span><span>if</span><span> (part.</span><span>functionCall</span><span>) {</span></div></div><div><div><div>472</div></div><div><span>       </span><span>const</span><span> </span><span>fc</span><span> </span><span>=</span><span> part.</span><span>functionCall</span><span>;</span></div></div><div><div><div>473</div></div><div><span><span>       </span></span><span>message.</span><span>tool_calls</span><span> </span><span>=</span><span> message.</span><span>tool_calls</span><span> </span><span>??</span><span> [];</span></div></div><div><div><div>474</div></div><div><span><span>       </span></span><span>message.</span><span>tool_calls</span><span>.</span><span>push</span><span>({</span></div></div><div><div><div>475</div></div><div><span><span>         </span></span><span>id</span><span>:</span><span> fc.</span><span>id</span><span> </span><span>??</span><span> </span><span>`{</span><span>${</span><span>fc</span><span>.</span><span>name</span><span>}</span><span>}`</span><span>,</span></div></div><div><div><div>476</div></div><div><span><span>         </span></span><span>type</span><span>:</span><span> </span><span>"function"</span><span>,</span></div></div><div><div><div>477</div></div><div><span><span>         </span></span><span>function</span><span>:</span><span> {</span></div></div><div><div><div>478</div></div><div><span><span>           </span></span><span>name</span><span>:</span><span> fc.</span><span>name</span><span>,</span></div></div><div><div><div>479</div></div><div><span><span>           </span></span><span>arguments</span><span>:</span><span> </span><span>JSON</span><span>.</span><span>stringify</span><span>(fc.</span><span>args</span><span>),</span></div></div><div><div><div>480</div></div><div><span><span>         </span></span><span>}</span></div></div><div><div><div>481</div></div><div><span><span>       </span></span><span>});</span></div></div><div><div><div>482</div></div><div><span><span>     </span></span><span>} </span><span>else</span><span> {</span></div></div><div><div><div>483</div></div><div><span><span>       </span></span><span>message.</span><span>content</span><span>.</span><span>push</span><span>(part.</span><span>text</span><span>);</span></div></div><div><div><div>484</div></div><div><span><span>     </span></span><span>}</span></div></div><div><div><div>485</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>486</div></div><div><span><span>    </span></span><span>message.</span><span>content</span><span> </span><span>=</span><span> message.</span><span>content</span><span>.</span><span>join</span><span>(</span><span>SEP</span><span>) </span><span>||</span><span> </span><span>null</span><span>;</span></div></div><div><div><div>487</div></div><div><span>    </span><span>return</span><span> {</span></div></div><div><div><div>488</div></div><div><span><span>      </span></span><span>index</span><span>:</span><span> cand.</span><span>index</span><span> </span><span>||</span><span> </span><span>0</span><span>, </span><span>// 0-index is absent in new -002 models response</span></div></div><div><div><div>489</div></div><div><span><span>      </span></span><span>[key]</span><span>:</span><span> message,</span></div></div><div><div><div>490</div></div><div><span><span>      </span></span><span>logprobs</span><span>:</span><span> </span><span>null</span><span>,</span></div></div><div><div><div>491</div></div><div><span><span>      </span></span><span>finish_reason</span><span>:</span><span> message.</span><span>tool_calls</span><span> </span><span>?</span><span> </span><span>"tool_calls"</span><span> </span><span>:</span><span> reasonsMap[cand.</span><span>finishReason</span><span>] </span><span>||</span><span> cand.</span><span>finishReason</span><span>,</span></div></div><div><div><div>492</div></div><div><span>      </span><span>//original_finish_reason: cand.finishReason,</span></div></div><div><div><div>493</div></div><div><span><span>    </span></span><span>};</span></div></div><div><div><div>494</div></div><div><span>};</span></div></div><div><div><div>495</div></div><div>
</div></div><div><div><div>496</div></div><div><span>const</span><span> </span><span>transformCandidatesMessage</span><span> </span><span>=</span><span> transformCandidates.</span><span>bind</span><span>(</span><span>null</span><span>, </span><span>"message"</span><span>);</span></div></div><div><div><div>497</div></div><div><span>const</span><span> </span><span>transformCandidatesDelta</span><span> </span><span>=</span><span> transformCandidates.</span><span>bind</span><span>(</span><span>null</span><span>, </span><span>"delta"</span><span>);</span></div></div><div><div><div>498</div></div><div>
</div></div><div><div><div>499</div></div><div><span>const</span><span> </span><span>transformUsage</span><span> </span><span>=</span><span><span> (</span><span>data</span><span>) </span></span><span>=&gt;</span><span> ({</span></div></div><div><div><div>500</div></div><div><span><span>  </span></span><span>completion_tokens</span><span>:</span><span> data.</span><span>candidatesTokenCount</span><span>,</span></div></div><div><div><div>501</div></div><div><span><span>  </span></span><span>prompt_tokens</span><span>:</span><span> data.</span><span>promptTokenCount</span><span>,</span></div></div><div><div><div>502</div></div><div><span><span>  </span></span><span>total_tokens</span><span>:</span><span> data.</span><span>totalTokenCount</span></div></div><div><div><div>503</div></div><div><span>});</span></div></div><div><div><div>504</div></div><div>
</div></div><div><div><div>505</div></div><div><span>const</span><span> </span><span>checkPromptBlock</span><span> </span><span>=</span><span><span> (</span><span>choices</span><span>, </span><span>promptFeedback</span><span>, </span><span>key</span><span>) </span></span><span>=&gt;</span><span> {</span></div></div><div><div><div>506</div></div><div><span>   </span><span>if</span><span> (choices.</span><span>length</span><span>) { </span><span>return</span><span>; }</span></div></div><div><div><div>507</div></div><div><span>   </span><span>if</span><span> (promptFeedback?.</span><span>blockReason</span><span>) {</span></div></div><div><div><div>508</div></div><div><span><span>     </span></span><span>console.</span><span>log</span><span>(</span><span>"Prompt block reason:"</span><span>, promptFeedback.</span><span>blockReason</span><span>);</span></div></div><div><div><div>509</div></div><div><span>     </span><span>if</span><span> (promptFeedback.</span><span>blockReason</span><span> </span><span>===</span><span> </span><span>"SAFETY"</span><span>) {</span></div></div><div><div><div>510</div></div><div><span><span>       </span></span><span>promptFeedback.</span><span>safetyRatings</span></div></div><div><div><div>511</div></div><div><span><span>         </span></span><span>.</span><span>filter</span><span><span>(</span><span>r</span><span> </span></span><span>=&gt;</span><span> r.</span><span>blocked</span><span>)</span></div></div><div><div><div>512</div></div><div><span><span>         </span></span><span>.</span><span>forEach</span><span><span>(</span><span>r</span><span> </span></span><span>=&gt;</span><span> console.</span><span>log</span><span>(r));</span></div></div><div><div><div>513</div></div><div><span><span>     </span></span><span>}</span></div></div><div><div><div>514</div></div><div><span><span>     </span></span><span>choices.</span><span>push</span><span>({</span></div></div><div><div><div>515</div></div><div><span><span>       </span></span><span>index</span><span>:</span><span> </span><span>0</span><span>,</span></div></div><div><div><div>516</div></div><div><span><span>       </span></span><span>[key]</span><span>:</span><span> </span><span>null</span><span>,</span></div></div><div><div><div>517</div></div><div><span><span>       </span></span><span>finish_reason</span><span>:</span><span> </span><span>"content_filter"</span><span>,</span></div></div><div><div><div>518</div></div><div><span>       </span><span>//original_finish_reason: data.promptFeedback.blockReason,</span></div></div><div><div><div>519</div></div><div><span><span>     </span></span><span>});</span></div></div><div><div><div>520</div></div><div><span><span>   </span></span><span>}</span></div></div><div><div><div>521</div></div><div><span>   </span><span>return</span><span> </span><span>true</span><span>;</span></div></div><div><div><div>522</div></div><div><span>};</span></div></div><div><div><div>523</div></div><div>
</div></div><div><div><div>524</div></div><div><span>const</span><span> </span><span>processCompletionsResponse</span><span> </span><span>=</span><span><span> (</span><span>data</span><span>, </span><span>model</span><span>, </span><span>id</span><span>) </span></span><span>=&gt;</span><span> {</span></div></div><div><div><div>525</div></div><div><span>  </span><span>const</span><span> </span><span>obj</span><span> </span><span>=</span><span> {</span></div></div><div><div><div>526</div></div><div><span><span>    </span></span><span>id,</span></div></div><div><div><div>527</div></div><div><span><span>    </span></span><span>choices</span><span>:</span><span> data.</span><span>candidates</span><span>.</span><span>map</span><span>(transformCandidatesMessage),</span></div></div><div><div><div>528</div></div><div><span><span>    </span></span><span>created</span><span>:</span><span> Math.</span><span>floor</span><span>(Date.</span><span>now</span><span>() </span><span>/</span><span> </span><span>1000</span><span>),</span></div></div><div><div><div>529</div></div><div><span><span>    </span></span><span>model</span><span>:</span><span> data.</span><span>modelVersion</span><span> </span><span>??</span><span> model,</span></div></div><div><div><div>530</div></div><div><span><span>    </span></span><span>object</span><span>:</span><span> </span><span>"chat.completion"</span><span>,</span></div></div><div><div><div>531</div></div><div><span><span>    </span></span><span>usage</span><span>:</span><span> data.</span><span>usageMetadata</span><span> </span><span>&amp;&amp;</span><span> </span><span>transformUsage</span><span>(data.</span><span>usageMetadata</span><span>),</span></div></div><div><div><div>532</div></div><div><span><span>  </span></span><span>};</span></div></div><div><div><div>533</div></div><div><span>  </span><span>if</span><span> (obj.</span><span>choices</span><span>.</span><span>length</span><span> </span><span>===</span><span> </span><span>0</span><span> ) {</span></div></div><div><div><div>534</div></div><div><span>    </span><span>checkPromptBlock</span><span>(obj.</span><span>choices</span><span>, data.</span><span>promptFeedback</span><span>, </span><span>"message"</span><span>);</span></div></div><div><div><div>535</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>536</div></div><div><span>  </span><span>return</span><span> </span><span>JSON</span><span>.</span><span>stringify</span><span>(obj);</span></div></div><div><div><div>537</div></div><div><span>};</span></div></div><div><div><div>538</div></div><div>
</div></div><div><div><div>539</div></div><div><span>const</span><span> </span><span>responseLineRE</span><span> </span><span>=</span><span><span> </span><span>/</span></span><span>^</span><span>data: (</span><span>.</span><span><span>*</span><span>)(?:</span></span><span>\n\n</span><span>|</span><span>\r\r</span><span>|</span><span>\r\n\r\n</span><span><span>)</span><span>/</span></span><span>;</span></div></div><div><div><div>540</div></div><div><span>function</span><span> </span><span>parseStream</span><span><span> (</span><span>chunk</span><span>, </span><span>controller</span><span>) {</span></span></div></div><div><div><div>541</div></div><div><span>  </span><span>this</span><span>.</span><span>buffer</span><span> </span><span>+=</span><span> chunk;</span></div></div><div><div><div>542</div></div><div><span>  </span><span>do</span><span> {</span></div></div><div><div><div>543</div></div><div><span>    </span><span>const</span><span> </span><span>match</span><span> </span><span>=</span><span> </span><span>this</span><span>.</span><span>buffer</span><span>.</span><span>match</span><span>(responseLineRE);</span></div></div><div><div><div>544</div></div><div><span>    </span><span>if</span><span> (</span><span>!</span><span>match) { </span><span>break</span><span>; }</span></div></div><div><div><div>545</div></div><div><span><span>    </span></span><span>controller.</span><span>enqueue</span><span>(match[</span><span>1</span><span>]);</span></div></div><div><div><div>546</div></div><div><span>    </span><span>this</span><span>.</span><span>buffer</span><span> </span><span>=</span><span> </span><span>this</span><span>.</span><span>buffer</span><span>.</span><span>substring</span><span>(match[</span><span>0</span><span>].</span><span>length</span><span>);</span></div></div><div><div><div>547</div></div><div><span><span>  </span></span><span>} </span><span>while</span><span> (</span><span>true</span><span>);</span></div></div><div><div><div>548</div></div><div><span>}</span></div></div><div><div><div>549</div></div><div>
</div></div><div><div><div>550</div></div><div><span>function</span><span> </span><span>parseStreamFlush</span><span><span> (</span><span>controller</span><span>) {</span></span></div></div><div><div><div>551</div></div><div><span>  </span><span>if</span><span> (</span><span>this</span><span>.</span><span>buffer</span><span>) {</span></div></div><div><div><div>552</div></div><div><span><span>    </span></span><span>console.</span><span>error</span><span>(</span><span>"Invalid data:"</span><span>, </span><span>this</span><span>.</span><span>buffer</span><span>);</span></div></div><div><div><div>553</div></div><div><span><span>    </span></span><span>controller.</span><span>enqueue</span><span>(</span><span>this</span><span>.</span><span>buffer</span><span>);</span></div></div><div><div><div>554</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>555</div></div><div><span>}</span></div></div><div><div><div>556</div></div><div>
</div></div><div><div><div>557</div></div><div>
</div></div><div><div><div>558</div></div><div><span>const</span><span> </span><span>delimiter</span><span> </span><span>=</span><span> </span><span>"</span><span>\n\n</span><span>"</span><span>;</span></div></div><div><div><div>559</div></div><div><span>const</span><span> </span><span>sseline</span><span> </span><span>=</span><span><span> (</span><span>obj</span><span>) </span></span><span>=&gt;</span><span> {</span></div></div><div><div><div>560</div></div><div><span><span>  </span></span><span>obj.</span><span>created</span><span> </span><span>=</span><span> Math.</span><span>floor</span><span>(Date.</span><span>now</span><span>() </span><span>/</span><span> </span><span>1000</span><span>);</span></div></div><div><div><div>561</div></div><div><span>  </span><span>return</span><span> </span><span>"data: "</span><span> </span><span>+</span><span> </span><span>JSON</span><span>.</span><span>stringify</span><span>(obj) </span><span>+</span><span> delimiter;</span></div></div><div><div><div>562</div></div><div><span>};</span></div></div><div><div><div>563</div></div><div>
</div></div><div><div><div>564</div></div><div><span>function</span><span> </span><span>toOpenAiStream</span><span><span> (</span><span>line</span><span>, </span><span>controller</span><span>) {</span></span></div></div><div><div><div>565</div></div><div><span>  </span><span>let</span><span> data;</span></div></div><div><div><div>566</div></div><div><span>  </span><span>try</span><span> {</span></div></div><div><div><div>567</div></div><div><span><span>    </span></span><span>data </span><span>=</span><span> </span><span>JSON</span><span>.</span><span>parse</span><span>(line);</span></div></div><div><div><div>568</div></div><div><span>    </span><span>if</span><span> (</span><span>!</span><span>data.</span><span>candidates</span><span>) {</span></div></div><div><div><div>569</div></div><div><span>      </span><span>throw</span><span> </span><span>new</span><span> </span><span>Error</span><span>(</span><span>"Invalid completion chunk object"</span><span>);</span></div></div><div><div><div>570</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>571</div></div><div><span><span>  </span></span><span>} </span><span>catch</span><span> (err) {</span></div></div><div><div><div>572</div></div><div><span><span>    </span></span><span>console.</span><span>error</span><span>(</span><span>"Error parsing response:"</span><span>, err);</span></div></div><div><div><div>573</div></div><div><span><span>    </span></span><span>controller.</span><span>enqueue</span><span>(line); </span><span>// output as is</span></div></div><div><div><div>574</div></div><div><span>    </span><span>return</span><span>;</span></div></div><div><div><div>575</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>576</div></div><div>
</div></div><div><div><div>577</div></div><div><span>  </span><span>const</span><span> </span><span>obj</span><span> </span><span>=</span><span> {</span></div></div><div><div><div>578</div></div><div><span><span>    </span></span><span>id</span><span>:</span><span> </span><span>this</span><span>.</span><span>id</span><span>,</span></div></div><div><div><div>579</div></div><div><span><span>    </span></span><span>choices</span><span>:</span><span> data.</span><span>candidates</span><span>.</span><span>map</span><span>(transformCandidatesDelta),</span></div></div><div><div><div>580</div></div><div><span>    </span><span>//created: Math.floor(Date.now()/1000),</span></div></div><div><div><div>581</div></div><div><span><span>    </span></span><span>model</span><span>:</span><span> data.</span><span>modelVersion</span><span> </span><span>??</span><span> </span><span>this</span><span>.</span><span>model</span><span>,</span></div></div><div><div><div>582</div></div><div><span>    </span><span>//system_fingerprint: "fp_69829325d0",</span></div></div><div><div><div>583</div></div><div><span><span>    </span></span><span>object</span><span>:</span><span> </span><span>"chat.completion.chunk"</span><span>,</span></div></div><div><div><div>584</div></div><div><span><span>    </span></span><span>usage</span><span>:</span><span> data.</span><span>usageMetadata</span><span> </span><span>&amp;&amp;</span><span> </span><span>this</span><span>.</span><span>streamIncludeUsage</span><span> </span><span>?</span><span> </span><span>null</span><span> </span><span>:</span><span> </span><span>undefined</span><span>,</span></div></div><div><div><div>585</div></div><div><span><span>  </span></span><span>};</span></div></div><div><div><div>586</div></div><div><span>  </span><span>if</span><span> (</span><span>checkPromptBlock</span><span>(obj.</span><span>choices</span><span>, data.</span><span>promptFeedback</span><span>, </span><span>"delta"</span><span>)) {</span></div></div><div><div><div>587</div></div><div><span><span>    </span></span><span>controller.</span><span>enqueue</span><span>(</span><span>sseline</span><span>(obj));</span></div></div><div><div><div>588</div></div><div><span>    </span><span>return</span><span>;</span></div></div><div><div><div>589</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>590</div></div><div><span><span>  </span></span><span>console.</span><span>assert</span><span>(data.</span><span>candidates</span><span>.</span><span>length</span><span> </span><span>===</span><span> </span><span>1</span><span>, </span><span>"Unexpected candidates count: %d"</span><span>, data.</span><span>candidates</span><span>.</span><span>length</span><span>);</span></div></div><div><div><div>591</div></div><div><span>  </span><span>const</span><span> </span><span>cand</span><span> </span><span>=</span><span> obj.</span><span>choices</span><span>[</span><span>0</span><span>];</span></div></div><div><div><div>592</div></div><div><span><span>  </span></span><span>cand.</span><span>index</span><span> </span><span>=</span><span> cand.</span><span>index</span><span> </span><span>||</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>593</div></div><div><span>  </span><span>const</span><span> </span><span>finish_reason</span><span> </span><span>=</span><span> cand.</span><span>finish_reason</span><span>;</span></div></div><div><div><div>594</div></div><div><span><span>  </span></span><span>cand.</span><span>finish_reason</span><span> </span><span>=</span><span> </span><span>null</span><span>;</span></div></div><div><div><div>595</div></div><div><span>  </span><span>if</span><span> (</span><span>!</span><span>this</span><span>.</span><span>last</span><span>[cand.</span><span>index</span><span>]) { </span><span>// first</span></div></div><div><div><div>596</div></div><div><span><span>    </span></span><span>controller.</span><span>enqueue</span><span>(</span><span>sseline</span><span>({</span></div></div><div><div><div>597</div></div><div><span>      </span><span>...</span><span>obj,</span></div></div><div><div><div>598</div></div><div><span><span>      </span></span><span>choices</span><span>:</span><span> [{ </span><span>...</span><span>cand, </span><span>tool_calls</span><span>:</span><span> </span><span>undefined</span><span>, </span><span>delta</span><span>:</span><span> { </span><span>role</span><span>:</span><span> </span><span>"assistant"</span><span>, </span><span>content</span><span>:</span><span> </span><span>""</span><span> } }],</span></div></div><div><div><div>599</div></div><div><span><span>    </span></span><span>}));</span></div></div><div><div><div>600</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>601</div></div><div><span>  </span><span>delete</span><span> cand.</span><span>delta</span><span>.</span><span>role</span><span>;</span></div></div><div><div><div>602</div></div><div><span>  </span><span>if</span><span> (</span><span>"content"</span><span> </span><span>in</span><span> cand.</span><span>delta</span><span>) { </span><span>// prevent empty data (e.g. when MAX_TOKENS)</span></div></div><div><div><div>603</div></div><div><span><span>    </span></span><span>controller.</span><span>enqueue</span><span>(</span><span>sseline</span><span>(obj));</span></div></div><div><div><div>604</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>605</div></div><div><span><span>  </span></span><span>cand.</span><span>finish_reason</span><span> </span><span>=</span><span> finish_reason;</span></div></div><div><div><div>606</div></div><div><span>  </span><span>if</span><span> (data.</span><span>usageMetadata</span><span> </span><span>&amp;&amp;</span><span> </span><span>this</span><span>.</span><span>streamIncludeUsage</span><span>) {</span></div></div><div><div><div>607</div></div><div><span><span>    </span></span><span>obj.</span><span>usage</span><span> </span><span>=</span><span> </span><span>transformUsage</span><span>(data.</span><span>usageMetadata</span><span>);</span></div></div><div><div><div>608</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>609</div></div><div><span><span>  </span></span><span>cand.</span><span>delta</span><span> </span><span>=</span><span> {};</span></div></div><div><div><div>610</div></div><div><span>  </span><span>this</span><span>.</span><span>last</span><span>[cand.</span><span>index</span><span>] </span><span>=</span><span> obj;</span></div></div><div><div><div>611</div></div><div><span>}</span></div></div><div><div><div>612</div></div><div>
</div></div><div><div><div>613</div></div><div><span>function</span><span> </span><span>toOpenAiStreamFlush</span><span><span> (</span><span>controller</span><span>) {</span></span></div></div><div><div><div>614</div></div><div><span>  </span><span>if</span><span> (</span><span>this</span><span>.</span><span>last</span><span>.</span><span>length</span><span> </span><span>&gt;</span><span> </span><span>0</span><span>) {</span></div></div><div><div><div>615</div></div><div><span>    </span><span>for</span><span> (</span><span>const</span><span> </span><span>obj</span><span> </span><span>of</span><span> </span><span>this</span><span>.</span><span>last</span><span>) {</span></div></div><div><div><div>616</div></div><div><span><span>      </span></span><span>controller.</span><span>enqueue</span><span>(</span><span>sseline</span><span>(obj));</span></div></div><div><div><div>617</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>618</div></div><div><span><span>    </span></span><span>controller.</span><span>enqueue</span><span>(</span><span>"data: [DONE]"</span><span> </span><span>+</span><span> delimiter);</span></div></div><div><div><div>619</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>620</div></div><div><span>}</span></div></div><div><div><div>621</div></div><div>
</div></div><div><div><div>622</div></div><div><span>const</span><span> </span><span>errHandler</span><span> </span><span>=</span><span><span> (</span><span>err</span><span>) </span></span><span>=&gt;</span><span> {</span></div></div><div><div><div>623</div></div><div><span><span>  </span></span><span>console.</span><span>error</span><span>(err);</span></div></div><div><div><div>624</div></div><div><span>  </span><span>return</span><span> </span><span>new</span><span> </span><span>Response</span><span>(err.</span><span>message</span><span>, </span><span>fixCors</span><span>({ </span><span>status</span><span>:</span><span> err.</span><span>status</span><span> </span><span>??</span><span> </span><span>500</span><span> }));</span></div></div><div><div><div>625</div></div><div><span>};</span></div></div><div><div><div>626</div></div><div>
</div></div><div><div><div>627</div></div><div><span>function</span><span> </span><span>extractPrefixAndRest</span><span><span>(</span><span>pathname</span><span>, </span><span>prefixes</span><span>) {</span></span></div></div><div><div><div>628</div></div><div><span>  </span><span>for</span><span> (</span><span>const</span><span> </span><span>prefix</span><span> </span><span>of</span><span> prefixes) {</span></div></div><div><div><div>629</div></div><div><span>    </span><span>if</span><span> (pathname.</span><span>startsWith</span><span>(prefix)) {</span></div></div><div><div><div>630</div></div><div><span>      </span><span>return</span><span> [prefix, pathname.</span><span>slice</span><span>(prefix.</span><span>length</span><span>)];</span></div></div><div><div><div>631</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>632</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>633</div></div><div><span>  </span><span>return</span><span> [</span><span>null</span><span>, </span><span>null</span><span>];</span></div></div><div><div><div>634</div></div><div><span>}</span></div></div><div><div><div>635</div></div><div>
</div></div><div><div><div>636</div></div><div><span>serve</span><span>(</span><span>async</span><span><span> (</span><span>request</span><span>) </span></span><span>=&gt;</span><span> {</span></div></div><div><div><div>637</div></div><div><span>  </span><span>const</span><span> </span><span>url</span><span> </span><span>=</span><span> </span><span>new</span><span> </span><span>URL</span><span>(request.</span><span>url</span><span>);</span></div></div><div><div><div>638</div></div><div><span>  </span><span>const</span><span> </span><span>pathname</span><span> </span><span>=</span><span> url.</span><span>pathname</span><span>;</span></div></div><div><div><div>639</div></div><div>
</div></div><div><div><div>640</div></div><div><span>  </span><span>if</span><span> (pathname </span><span>===</span><span> </span><span>'/'</span><span> </span><span>||</span><span> pathname </span><span>===</span><span> </span><span>'/index.html'</span><span>) {</span></div></div><div><div><div>641</div></div><div><span>    </span><span>return</span><span> </span><span>new</span><span> </span><span>Response</span><span>(</span><span>'Service is running!'</span><span>, {</span></div></div><div><div><div>642</div></div><div><span><span>      </span></span><span>status</span><span>:</span><span> </span><span>200</span><span>,</span></div></div><div><div><div>643</div></div><div><span><span>      </span></span><span>headers</span><span>:</span><span> { </span><span>'Content-Type'</span><span>:</span><span> </span><span>'text/html'</span><span> }</span></div></div><div><div><div>644</div></div><div><span><span>    </span></span><span>});</span></div></div><div><div><div>645</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>646</div></div><div>
</div></div><div><div><div>647</div></div><div><span>  </span><span>if</span><span> (pathname </span><span>===</span><span> </span><span>'/robots.txt'</span><span>) {</span></div></div><div><div><div>648</div></div><div><span>    </span><span>return</span><span> </span><span>new</span><span> </span><span>Response</span><span>(</span><span>'User-agent: *</span><span>\n</span><span>Disallow: /'</span><span>, {</span></div></div><div><div><div>649</div></div><div><span><span>      </span></span><span>status</span><span>:</span><span> </span><span>200</span><span>,</span></div></div><div><div><div>650</div></div><div><span><span>      </span></span><span>headers</span><span>:</span><span> { </span><span>'Content-Type'</span><span>:</span><span> </span><span>'text/plain'</span><span> }</span></div></div><div><div><div>651</div></div><div><span><span>    </span></span><span>});</span></div></div><div><div><div>652</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>653</div></div><div>
</div></div><div><div><div>654</div></div><div><span>  </span><span>// 特殊处理 /gemini 路径</span></div></div><div><div><div>655</div></div><div><span>  </span><span>if</span><span> (pathname.</span><span>startsWith</span><span>(</span><span>'/gemini'</span><span>)) {</span></div></div><div><div><div>656</div></div><div><span>    </span><span>if</span><span> (request.</span><span>method</span><span> </span><span>===</span><span> </span><span>"OPTIONS"</span><span>) {</span></div></div><div><div><div>657</div></div><div><span>      </span><span>return</span><span> </span><span>handleOPTIONS</span><span>();</span></div></div><div><div><div>658</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>659</div></div><div>
</div></div><div><div><div>660</div></div><div><span>    </span><span>try</span><span> {</span></div></div><div><div><div>661</div></div><div><span>      </span><span>const</span><span> </span><span>auth</span><span> </span><span>=</span><span> request.</span><span>headers</span><span>.</span><span>get</span><span>(</span><span>"Authorization"</span><span>);</span></div></div><div><div><div>662</div></div><div><span>      </span><span>const</span><span> </span><span>apiKey</span><span> </span><span>=</span><span> auth?.</span><span>split</span><span>(</span><span>" "</span><span>)[</span><span>1</span><span>];</span></div></div><div><div><div>663</div></div><div>
</div></div><div><div><div>664</div></div><div><span>      </span><span>if</span><span> (pathname.</span><span>endsWith</span><span>(</span><span>"/chat/completions"</span><span>)) {</span></div></div><div><div><div>665</div></div><div><span>        </span><span>return</span><span> </span><span>handleCompletions</span><span>(</span><span>await</span><span> request.</span><span>json</span><span>(), apiKey)</span></div></div><div><div><div>666</div></div><div><span><span>          </span></span><span>.</span><span>catch</span><span>(errHandler);</span></div></div><div><div><div>667</div></div><div><span><span>      </span></span><span>} </span><span>else</span><span> </span><span>if</span><span> (pathname.</span><span>endsWith</span><span>(</span><span>"/embeddings"</span><span>)) {</span></div></div><div><div><div>668</div></div><div><span>        </span><span>return</span><span> </span><span>handleEmbeddings</span><span>(</span><span>await</span><span> request.</span><span>json</span><span>(), apiKey)</span></div></div><div><div><div>669</div></div><div><span><span>          </span></span><span>.</span><span>catch</span><span>(errHandler);</span></div></div><div><div><div>670</div></div><div><span><span>      </span></span><span>} </span><span>else</span><span> </span><span>if</span><span> (pathname.</span><span>endsWith</span><span>(</span><span>"/models"</span><span>)) {</span></div></div><div><div><div>671</div></div><div><span>        </span><span>return</span><span> </span><span>handleModels</span><span>(apiKey)</span></div></div><div><div><div>672</div></div><div><span><span>          </span></span><span>.</span><span>catch</span><span>(errHandler);</span></div></div><div><div><div>673</div></div><div><span><span>      </span></span><span>} </span><span>else</span><span> {</span></div></div><div><div><div>674</div></div><div><span>        </span><span>throw</span><span> </span><span>new</span><span> </span><span>HttpError</span><span>(</span><span>"404 Not Found"</span><span>, </span><span>404</span><span>);</span></div></div><div><div><div>675</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>676</div></div><div><span><span>    </span></span><span>} </span><span>catch</span><span> (err) {</span></div></div><div><div><div>677</div></div><div><span>      </span><span>return</span><span> </span><span>errHandler</span><span>(err);</span></div></div><div><div><div>678</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>679</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>680</div></div><div>
</div></div><div><div><div>681</div></div><div><span>  </span><span>// 处理其他 API 路径</span></div></div><div><div><div>682</div></div><div><span>  </span><span>const</span><span> [</span><span>prefix</span><span>, </span><span>rest</span><span>] </span><span>=</span><span> </span><span>extractPrefixAndRest</span><span>(pathname, Object.</span><span>keys</span><span>(apiMapping));</span></div></div><div><div><div>683</div></div><div><span>  </span><span>if</span><span> (</span><span>!</span><span>prefix) {</span></div></div><div><div><div>684</div></div><div><span>    </span><span>return</span><span> </span><span>new</span><span> </span><span>Response</span><span>(</span><span>'Not Found'</span><span>, { </span><span>status</span><span>:</span><span> </span><span>404</span><span> });</span></div></div><div><div><div>685</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>686</div></div><div>
</div></div><div><div><div>687</div></div><div><span>  </span><span>const</span><span> </span><span>targetUrl</span><span> </span><span>=</span><span> </span><span>`</span><span>${</span><span><span>apiMapping</span><span>[</span><span>prefix</span><span>]</span></span><span>}${</span><span>rest</span><span>}</span><span>`</span><span>;</span></div></div><div><div><div>688</div></div><div>
</div></div><div><div><div>689</div></div><div><span>  </span><span>try</span><span> {</span></div></div><div><div><div>690</div></div><div><span>    </span><span>const</span><span> </span><span>headers</span><span> </span><span>=</span><span> </span><span>new</span><span> </span><span>Headers</span><span>();</span></div></div><div><div><div>691</div></div><div><span>    </span><span>const</span><span> </span><span>allowedHeaders</span><span> </span><span>=</span><span> [</span><span>'accept'</span><span>, </span><span>'content-type'</span><span>, </span><span>'authorization'</span><span>];</span></div></div><div><div><div>692</div></div><div><span>    </span><span>for</span><span> (</span><span>const</span><span> [</span><span>key</span><span>, </span><span>value</span><span>] </span><span>of</span><span> request.</span><span>headers</span><span>.</span><span>entries</span><span>()) {</span></div></div><div><div><div>693</div></div><div><span>      </span><span>if</span><span> (allowedHeaders.</span><span>includes</span><span>(key.</span><span>toLowerCase</span><span>())) {</span></div></div><div><div><div>694</div></div><div><span><span>        </span></span><span>headers.</span><span>set</span><span>(key, value);</span></div></div><div><div><div>695</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>696</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>697</div></div><div>
</div></div><div><div><div>698</div></div><div><span>    </span><span>const</span><span> </span><span>response</span><span> </span><span>=</span><span> </span><span>await</span><span> </span><span>fetch</span><span>(targetUrl, {</span></div></div><div><div><div>699</div></div><div><span><span>      </span></span><span>method</span><span>:</span><span> request.</span><span>method</span><span>,</span></div></div><div><div><div>700</div></div><div><span><span>      </span></span><span>headers</span><span>:</span><span> headers,</span></div></div><div><div><div>701</div></div><div><span><span>      </span></span><span>body</span><span>:</span><span> request.</span><span>body</span></div></div><div><div><div>702</div></div><div><span><span>    </span></span><span>});</span></div></div><div><div><div>703</div></div><div>
</div></div><div><div><div>704</div></div><div><span>    </span><span>const</span><span> </span><span>responseHeaders</span><span> </span><span>=</span><span> </span><span>new</span><span> </span><span>Headers</span><span>(response.</span><span>headers</span><span>);</span></div></div><div><div><div>705</div></div><div><span><span>    </span></span><span>responseHeaders.</span><span>set</span><span>(</span><span>'X-Content-Type-Options'</span><span>, </span><span>'nosniff'</span><span>);</span></div></div><div><div><div>706</div></div><div><span><span>    </span></span><span>responseHeaders.</span><span>set</span><span>(</span><span>'X-Frame-Options'</span><span>, </span><span>'DENY'</span><span>);</span></div></div><div><div><div>707</div></div><div><span><span>    </span></span><span>responseHeaders.</span><span>set</span><span>(</span><span>'Referrer-Policy'</span><span>, </span><span>'no-referrer'</span><span>);</span></div></div><div><div><div>708</div></div><div>
</div></div><div><div><div>709</div></div><div><span>    </span><span>return</span><span> </span><span>new</span><span> </span><span>Response</span><span>(response.</span><span>body</span><span>, {</span></div></div><div><div><div>710</div></div><div><span><span>      </span></span><span>status</span><span>:</span><span> response.</span><span>status</span><span>,</span></div></div><div><div><div>711</div></div><div><span><span>      </span></span><span>headers</span><span>:</span><span> responseHeaders</span></div></div><div><div><div>712</div></div><div><span><span>    </span></span><span>});</span></div></div><div><div><div>713</div></div><div>
</div></div><div><div><div>714</div></div><div><span><span>  </span></span><span>} </span><span>catch</span><span> (error) {</span></div></div><div><div><div>715</div></div><div><span><span>    </span></span><span>console.</span><span>error</span><span>(</span><span>'Failed to fetch:'</span><span>, error);</span></div></div><div><div><div>716</div></div><div><span>    </span><span>return</span><span> </span><span>new</span><span> </span><span>Response</span><span>(</span><span>'Internal Server Error'</span><span>, { </span><span>status</span><span>:</span><span> </span><span>500</span><span> });</span></div></div><div><div><div>717</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>718</div></div><div><span>});</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section><section><h3>食用方法<a href="#食用方法"><span>#</span></a></h3><ol>
<li>复制上面的代码。</li>
<li>打开 <a href="https://dash.deno.com" target="_blank">Deno Playground</a>。</li>
<li>用github账号登录。
<figure><img src="https://i.111666.best/image/ev3jS05Ohuu4pyGiv7O270.png" alt="image" /><figcaption>image</figcaption></figure></li>
<li>点击蓝色的 <code>New Playground</code> 按钮。
<figure><img src="https://i.111666.best/image/0rSkYIFxyH04upWw1EhDWj.png" alt="image" /><figcaption>image</figcaption></figure></li>
<li>粘贴代码到左边的编辑器中。</li>
<li>点击上方的 <code>Save &amp; Deploy</code> 按钮。</li>
<li>当右边的终端显示 <code>Service is running!</code> 时,你的服务就已经部署成功了。</li>
<li>在<code>Settings</code>中可以设置你的域名。
<figure><img src="https://i.111666.best/image/1Mge6Q5OFH4xuyDamPfPTt.png" alt="image" /><figcaption>image</figcaption></figure></li>
<li>可以参考下面的表格,将BASE_URL改成对应的代理地址。
例如: <code>BASE_URL = "https://api.openai.com";</code> 改成 <code>BASE_URL = "https://你的地址/openai";</code>
请求就会变成 <code>https://你的地址/openai/v1/completions</code>。</li>
</ol><div><div><div></div><div>Important</div></div><div><p>Gemini已经转成了Openai格式，所以Gemini的请求地址也要改成Openai格式的。不能直接使用Gemini的请求格式。
如果只想单纯的代理Gemini的请求，可以部署下面的代码。</p></div></div><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>import</span><span> { </span><span>serve</span><span> } </span><span>from</span><span> </span><span>"https://deno.land/std/http/server.ts"</span><span>;</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>const</span><span> </span><span>apiMapping</span><span> </span><span>=</span><span> {</span></div></div><div><div><div>4</div></div><div><span>  </span><span>'/discord'</span><span>:</span><span> </span><span>'https://discord.com/api'</span><span>,</span></div></div><div><div><div>5</div></div><div><span>  </span><span>'/telegram'</span><span>:</span><span> </span><span>'https://api.telegram.org'</span><span>,</span></div></div><div><div><div>6</div></div><div><span>  </span><span>'/openai'</span><span>:</span><span> </span><span>'https://api.openai.com'</span><span>,</span></div></div><div><div><div>7</div></div><div><span>  </span><span>'/claude'</span><span>:</span><span> </span><span>'https://api.anthropic.com'</span><span>,</span></div></div><div><div><div>8</div></div><div><span>  </span><span>'/meta'</span><span>:</span><span> </span><span>'https://www.meta.ai/api'</span><span>,</span></div></div><div><div><div>9</div></div><div><span>  </span><span>'/groq'</span><span>:</span><span> </span><span>'https://api.groq.com/openai'</span><span>,</span></div></div><div><div><div>10</div></div><div><span>  </span><span>'/xai'</span><span>:</span><span> </span><span>'https://api.x.ai'</span><span>,</span></div></div><div><div><div>11</div></div><div><span>  </span><span>'/cohere'</span><span>:</span><span> </span><span>'https://api.cohere.ai'</span><span>,</span></div></div><div><div><div>12</div></div><div><span>  </span><span>'/huggingface'</span><span>:</span><span> </span><span>'https://api-inference.huggingface.co'</span><span>,</span></div></div><div><div><div>13</div></div><div><span>  </span><span>'/together'</span><span>:</span><span> </span><span>'https://api.together.xyz'</span><span>,</span></div></div><div><div><div>14</div></div><div><span>  </span><span>'/novita'</span><span>:</span><span> </span><span>'https://api.novita.ai'</span><span>,</span></div></div><div><div><div>15</div></div><div><span>  </span><span>'/portkey'</span><span>:</span><span> </span><span>'https://api.portkey.ai'</span><span>,</span></div></div><div><div><div>16</div></div><div><span>  </span><span>'/fireworks'</span><span>:</span><span> </span><span>'https://api.fireworks.ai'</span><span>,</span></div></div><div><div><div>17</div></div><div><span>  </span><span>'/openrouter'</span><span>:</span><span> </span><span>'https://openrouter.ai/api'</span><span>,</span></div></div><div><div><div>18</div></div><div><span>  </span><span>'/nvidia'</span><span>:</span><span> </span><span>'https://integrate.api.nvidia.com'</span><span>,</span></div></div><div><div><div>19</div></div><div><span>  </span><span>'/cerebras'</span><span>:</span><span> </span><span>'https://api.cerebras.ai'</span><span>,</span></div></div><div><div><div>20</div></div><div><span>  </span><span>'/sambanova'</span><span>:</span><span> </span><span>'https://api.sambanova.ai'</span><span>,</span></div></div><div><div><div>21</div></div><div><span>  </span><span>'/gemini'</span><span>:</span><span> </span><span>'https://generativelanguage.googleapis.com'</span><span>,</span></div></div><div><div><div>22</div></div><div><span>  </span><span>'/mistral'</span><span>:</span><span> </span><span>'https://api.mistral.ai'</span></div></div><div><div><div>23</div></div><div><span>};</span></div></div><div><div><div>24</div></div><div>
</div></div><div><div><div>25</div></div><div><span>const</span><span> </span><span>CORS_HEADERS</span><span> </span><span>=</span><span> {</span></div></div><div><div><div>26</div></div><div><span>  </span><span>"access-control-allow-origin"</span><span>:</span><span> </span><span>"*"</span><span>,</span></div></div><div><div><div>27</div></div><div><span>  </span><span>"access-control-allow-methods"</span><span>:</span><span> </span><span>"*"</span><span>,</span></div></div><div><div><div>28</div></div><div><span>  </span><span>"access-control-allow-headers"</span><span>:</span><span> </span><span>"*"</span><span>,</span></div></div><div><div><div>29</div></div><div><span>};</span></div></div><div><div><div>30</div></div><div>
</div></div><div><div><div>31</div></div><div><span>serve</span><span>(</span><span>async</span><span><span> (</span><span>request</span><span>) </span></span><span>=&gt;</span><span> {</span></div></div><div><div><div>32</div></div><div><span>  </span><span>// 处理 CORS 预检请求</span></div></div><div><div><div>33</div></div><div><span>  </span><span>if</span><span> (request.</span><span>method</span><span> </span><span>===</span><span> </span><span>"OPTIONS"</span><span>) {</span></div></div><div><div><div>34</div></div><div><span>    </span><span>return</span><span> </span><span>new</span><span> </span><span>Response</span><span>(</span><span>null</span><span>, {</span></div></div><div><div><div>35</div></div><div><span><span>      </span></span><span>headers</span><span>:</span><span> </span><span>CORS_HEADERS</span><span>,</span></div></div><div><div><div>36</div></div><div><span><span>    </span></span><span>});</span></div></div><div><div><div>37</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>38</div></div><div>
</div></div><div><div><div>39</div></div><div><span>  </span><span>const</span><span> </span><span>url</span><span> </span><span>=</span><span> </span><span>new</span><span> </span><span>URL</span><span>(request.</span><span>url</span><span>);</span></div></div><div><div><div>40</div></div><div><span>  </span><span>const</span><span> </span><span>pathname</span><span> </span><span>=</span><span> url.</span><span>pathname</span><span>;</span></div></div><div><div><div>41</div></div><div>
</div></div><div><div><div>42</div></div><div><span>  </span><span>if</span><span> (pathname </span><span>===</span><span> </span><span>'/'</span><span> </span><span>||</span><span> pathname </span><span>===</span><span> </span><span>'/index.html'</span><span>) {</span></div></div><div><div><div>43</div></div><div><span>    </span><span>return</span><span> </span><span>new</span><span> </span><span>Response</span><span>(</span><span>'Service is running!'</span><span>, {</span></div></div><div><div><div>44</div></div><div><span><span>      </span></span><span>status</span><span>:</span><span> </span><span>200</span><span>,</span></div></div><div><div><div>45</div></div><div><span><span>      </span></span><span>headers</span><span>:</span><span> {</span></div></div><div><div><div>46</div></div><div><span>        </span><span>'Content-Type'</span><span>:</span><span> </span><span>'text/html'</span><span>,</span></div></div><div><div><div>47</div></div><div><span>        </span><span>...</span><span>CORS_HEADERS</span></div></div><div><div><div>48</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>49</div></div><div><span><span>    </span></span><span>});</span></div></div><div><div><div>50</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>51</div></div><div>
</div></div><div><div><div>52</div></div><div><span>  </span><span>if</span><span> (pathname </span><span>===</span><span> </span><span>'/robots.txt'</span><span>) {</span></div></div><div><div><div>53</div></div><div><span>    </span><span>return</span><span> </span><span>new</span><span> </span><span>Response</span><span>(</span><span>'User-agent: *</span><span>\n</span><span>Disallow: /'</span><span>, {</span></div></div><div><div><div>54</div></div><div><span><span>      </span></span><span>status</span><span>:</span><span> </span><span>200</span><span>,</span></div></div><div><div><div>55</div></div><div><span><span>      </span></span><span>headers</span><span>:</span><span> {</span></div></div><div><div><div>56</div></div><div><span>        </span><span>'Content-Type'</span><span>:</span><span> </span><span>'text/plain'</span><span>,</span></div></div><div><div><div>57</div></div><div><span>        </span><span>...</span><span>CORS_HEADERS</span></div></div><div><div><div>58</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>59</div></div><div><span><span>    </span></span><span>});</span></div></div><div><div><div>60</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>61</div></div><div>
</div></div><div><div><div>62</div></div><div><span>  </span><span>const</span><span> [</span><span>prefix</span><span>, </span><span>rest</span><span>] </span><span>=</span><span> </span><span>extractPrefixAndRest</span><span>(pathname, Object.</span><span>keys</span><span>(apiMapping));</span></div></div><div><div><div>63</div></div><div><span>  </span><span>if</span><span> (</span><span>!</span><span>prefix) {</span></div></div><div><div><div>64</div></div><div><span>    </span><span>return</span><span> </span><span>new</span><span> </span><span>Response</span><span>(</span><span>'Not Found'</span><span>, { </span><span>status</span><span>:</span><span> </span><span>404</span><span> });</span></div></div><div><div><div>65</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>66</div></div><div>
</div></div><div><div><div>67</div></div><div><span>  </span><span>try</span><span> {</span></div></div><div><div><div>68</div></div><div><span>    </span><span>// 特殊处理 Gemini API 请求</span></div></div><div><div><div>69</div></div><div><span>    </span><span>if</span><span> (prefix </span><span>===</span><span> </span><span>'/gemini'</span><span>) {</span></div></div><div><div><div>70</div></div><div><span>      </span><span>const</span><span> </span><span>targetUrl</span><span> </span><span>=</span><span> </span><span>new</span><span> </span><span>URL</span><span>(</span><span>`</span><span>${</span><span><span>apiMapping</span><span>[</span><span>prefix</span><span>]</span></span><span>}${</span><span>rest</span><span>}</span><span>`</span><span>);</span></div></div><div><div><div>71</div></div><div>
</div></div><div><div><div>72</div></div><div><span>      </span><span>// 转发所有查询参数</span></div></div><div><div><div>73</div></div><div><span><span>      </span></span><span>url.</span><span>searchParams</span><span>.</span><span>forEach</span><span><span>((</span><span>value</span><span>, </span><span>key</span><span>) </span></span><span>=&gt;</span><span> {</span></div></div><div><div><div>74</div></div><div><span><span>        </span></span><span>targetUrl.</span><span>searchParams</span><span>.</span><span>append</span><span>(key, value);</span></div></div><div><div><div>75</div></div><div><span><span>      </span></span><span>});</span></div></div><div><div><div>76</div></div><div>
</div></div><div><div><div>77</div></div><div><span>      </span><span>const</span><span> </span><span>headers</span><span> </span><span>=</span><span> </span><span>new</span><span> </span><span>Headers</span><span>();</span></div></div><div><div><div>78</div></div><div><span>      </span><span>const</span><span> </span><span>geminiHeaders</span><span> </span><span>=</span><span> [</span></div></div><div><div><div>79</div></div><div><span>        </span><span>'accept'</span><span>,</span></div></div><div><div><div>80</div></div><div><span>        </span><span>'content-type'</span><span>,</span></div></div><div><div><div>81</div></div><div><span>        </span><span>'authorization'</span><span>,</span></div></div><div><div><div>82</div></div><div><span>        </span><span>'x-goog-api-client'</span><span>,</span></div></div><div><div><div>83</div></div><div><span>        </span><span>'x-goog-api-key'</span></div></div><div><div><div>84</div></div><div><span><span>      </span></span><span>];</span></div></div><div><div><div>85</div></div><div>
</div></div><div><div><div>86</div></div><div><span>      </span><span>for</span><span> (</span><span>const</span><span> [</span><span>key</span><span>, </span><span>value</span><span>] </span><span>of</span><span> request.</span><span>headers</span><span>.</span><span>entries</span><span>()) {</span></div></div><div><div><div>87</div></div><div><span>        </span><span>if</span><span> (geminiHeaders.</span><span>includes</span><span>(key.</span><span>toLowerCase</span><span>())) {</span></div></div><div><div><div>88</div></div><div><span><span>          </span></span><span>headers.</span><span>set</span><span>(key, value);</span></div></div><div><div><div>89</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>90</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>91</div></div><div>
</div></div><div><div><div>92</div></div><div><span>      </span><span>const</span><span> </span><span>response</span><span> </span><span>=</span><span> </span><span>await</span><span> </span><span>fetch</span><span>(targetUrl, {</span></div></div><div><div><div>93</div></div><div><span><span>        </span></span><span>method</span><span>:</span><span> request.</span><span>method</span><span>,</span></div></div><div><div><div>94</div></div><div><span><span>        </span></span><span>headers</span><span>:</span><span> headers,</span></div></div><div><div><div>95</div></div><div><span><span>        </span></span><span>body</span><span>:</span><span> request.</span><span>body</span></div></div><div><div><div>96</div></div><div><span><span>      </span></span><span>});</span></div></div><div><div><div>97</div></div><div>
</div></div><div><div><div>98</div></div><div><span>      </span><span>const</span><span> </span><span>responseHeaders</span><span> </span><span>=</span><span> </span><span>new</span><span> </span><span>Headers</span><span>({</span></div></div><div><div><div>99</div></div><div><span>        </span><span>...</span><span>CORS_HEADERS</span><span>,</span></div></div><div><div><div>100</div></div><div><span>        </span><span>...</span><span>Object.</span><span>fromEntries</span><span>(response.</span><span>headers</span><span>),</span></div></div><div><div><div>101</div></div><div><span>        </span><span>'X-Content-Type-Options'</span><span>:</span><span> </span><span>'nosniff'</span><span>,</span></div></div><div><div><div>102</div></div><div><span>        </span><span>'X-Frame-Options'</span><span>:</span><span> </span><span>'DENY'</span><span>,</span></div></div><div><div><div>103</div></div><div><span>        </span><span>'Referrer-Policy'</span><span>:</span><span> </span><span>'no-referrer'</span></div></div><div><div><div>104</div></div><div><span><span>      </span></span><span>});</span></div></div><div><div><div>105</div></div><div>
</div></div><div><div><div>106</div></div><div><span>      </span><span>return</span><span> </span><span>new</span><span> </span><span>Response</span><span>(response.</span><span>body</span><span>, {</span></div></div><div><div><div>107</div></div><div><span><span>        </span></span><span>status</span><span>:</span><span> response.</span><span>status</span><span>,</span></div></div><div><div><div>108</div></div><div><span><span>        </span></span><span>headers</span><span>:</span><span> responseHeaders</span></div></div><div><div><div>109</div></div><div><span><span>      </span></span><span>});</span></div></div><div><div><div>110</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>111</div></div><div>
</div></div><div><div><div>112</div></div><div><span>    </span><span>// 处理其他 API 请求</span></div></div><div><div><div>113</div></div><div><span>    </span><span>const</span><span> </span><span>targetUrl</span><span> </span><span>=</span><span> </span><span>`</span><span>${</span><span><span>apiMapping</span><span>[</span><span>prefix</span><span>]</span></span><span>}${</span><span>rest</span><span>}</span><span>`</span><span>;</span></div></div><div><div><div>114</div></div><div><span>    </span><span>const</span><span> </span><span>headers</span><span> </span><span>=</span><span> </span><span>new</span><span> </span><span>Headers</span><span>();</span></div></div><div><div><div>115</div></div><div><span>    </span><span>const</span><span> </span><span>allowedHeaders</span><span> </span><span>=</span><span> [</span><span>'accept'</span><span>, </span><span>'content-type'</span><span>, </span><span>'authorization'</span><span>];</span></div></div><div><div><div>116</div></div><div>
</div></div><div><div><div>117</div></div><div><span>    </span><span>for</span><span> (</span><span>const</span><span> [</span><span>key</span><span>, </span><span>value</span><span>] </span><span>of</span><span> request.</span><span>headers</span><span>.</span><span>entries</span><span>()) {</span></div></div><div><div><div>118</div></div><div><span>      </span><span>if</span><span> (allowedHeaders.</span><span>includes</span><span>(key.</span><span>toLowerCase</span><span>())) {</span></div></div><div><div><div>119</div></div><div><span><span>        </span></span><span>headers.</span><span>set</span><span>(key, value);</span></div></div><div><div><div>120</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>121</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>122</div></div><div>
</div></div><div><div><div>123</div></div><div><span>    </span><span>const</span><span> </span><span>response</span><span> </span><span>=</span><span> </span><span>await</span><span> </span><span>fetch</span><span>(targetUrl, {</span></div></div><div><div><div>124</div></div><div><span><span>      </span></span><span>method</span><span>:</span><span> request.</span><span>method</span><span>,</span></div></div><div><div><div>125</div></div><div><span><span>      </span></span><span>headers</span><span>:</span><span> headers,</span></div></div><div><div><div>126</div></div><div><span><span>      </span></span><span>body</span><span>:</span><span> request.</span><span>body</span></div></div><div><div><div>127</div></div><div><span><span>    </span></span><span>});</span></div></div><div><div><div>128</div></div><div>
</div></div><div><div><div>129</div></div><div><span>    </span><span>const</span><span> </span><span>responseHeaders</span><span> </span><span>=</span><span> </span><span>new</span><span> </span><span>Headers</span><span>(response.</span><span>headers</span><span>);</span></div></div><div><div><div>130</div></div><div><span><span>    </span></span><span>responseHeaders.</span><span>set</span><span>(</span><span>'X-Content-Type-Options'</span><span>, </span><span>'nosniff'</span><span>);</span></div></div><div><div><div>131</div></div><div><span><span>    </span></span><span>responseHeaders.</span><span>set</span><span>(</span><span>'X-Frame-Options'</span><span>, </span><span>'DENY'</span><span>);</span></div></div><div><div><div>132</div></div><div><span><span>    </span></span><span>responseHeaders.</span><span>set</span><span>(</span><span>'Referrer-Policy'</span><span>, </span><span>'no-referrer'</span><span>);</span></div></div><div><div><div>133</div></div><div>
</div></div><div><div><div>134</div></div><div><span>    </span><span>// 添加 CORS 头</span></div></div><div><div><div>135</div></div><div><span><span>    </span></span><span>Object.</span><span>entries</span><span>(</span><span>CORS_HEADERS</span><span>).</span><span>forEach</span><span><span>(([</span><span>key</span><span>, </span><span>value</span><span>]) </span></span><span>=&gt;</span><span> {</span></div></div><div><div><div>136</div></div><div><span><span>      </span></span><span>responseHeaders.</span><span>set</span><span>(key, value);</span></div></div><div><div><div>137</div></div><div><span><span>    </span></span><span>});</span></div></div><div><div><div>138</div></div><div>
</div></div><div><div><div>139</div></div><div><span>    </span><span>return</span><span> </span><span>new</span><span> </span><span>Response</span><span>(response.</span><span>body</span><span>, {</span></div></div><div><div><div>140</div></div><div><span><span>      </span></span><span>status</span><span>:</span><span> response.</span><span>status</span><span>,</span></div></div><div><div><div>141</div></div><div><span><span>      </span></span><span>headers</span><span>:</span><span> responseHeaders</span></div></div><div><div><div>142</div></div><div><span><span>    </span></span><span>});</span></div></div><div><div><div>143</div></div><div>
</div></div><div><div><div>144</div></div><div><span><span>  </span></span><span>} </span><span>catch</span><span> (error) {</span></div></div><div><div><div>145</div></div><div><span><span>    </span></span><span>console.</span><span>error</span><span>(</span><span>'Failed to fetch:'</span><span>, error);</span></div></div><div><div><div>146</div></div><div><span>    </span><span>return</span><span> </span><span>new</span><span> </span><span>Response</span><span>(</span><span>'Internal Server Error'</span><span>, { </span><span>status</span><span>:</span><span> </span><span>500</span><span> });</span></div></div><div><div><div>147</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>148</div></div><div><span>});</span></div></div><div><div><div>149</div></div><div>
</div></div><div><div><div>150</div></div><div><span>function</span><span> </span><span>extractPrefixAndRest</span><span><span>(</span><span>pathname</span></span><span>:</span><span> </span><span>string</span><span><span>, </span><span>prefixes</span></span><span>:</span><span> </span><span>string</span><span>[])</span><span>:</span><span> [</span><span>string</span><span> </span><span>|</span><span> </span><span>null</span><span>, </span><span>string</span><span> </span><span>|</span><span> </span><span>null</span><span>] {</span></div></div><div><div><div>151</div></div><div><span>  </span><span>for</span><span> (</span><span>const</span><span> </span><span>prefix</span><span> </span><span>of</span><span> prefixes) {</span></div></div><div><div><div>152</div></div><div><span>    </span><span>if</span><span> (pathname.</span><span>startsWith</span><span>(prefix)) {</span></div></div><div><div><div>153</div></div><div><span>      </span><span>return</span><span> [prefix, pathname.</span><span>slice</span><span>(prefix.</span><span>length</span><span>)];</span></div></div><div><div><div>154</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>155</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>156</div></div><div><span>  </span><span>return</span><span> [</span><span>null</span><span>, </span><span>null</span><span>];</span></div></div><div><div><div>157</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section><section><h3>代理地址<a href="#代理地址"><span>#</span></a></h3>

<table><thead><tr><th>代理地址</th><th>源地址</th></tr></thead><tbody><tr><td><code>https://你的地址/anthropic</code></td><td><code>https://api.anthropic.com</code></td></tr><tr><td><code>https://你的地址/cerebras</code></td><td><code>https://api.cerebras.ai</code></td></tr><tr><td><code>https://你的地址/cohere</code></td><td><code>https://api.cohere.ai</code></td></tr><tr><td><code>https://你的地址/discord</code></td><td><code>https://discord.com/api</code></td></tr><tr><td><code>https://你的地址/fireworks</code></td><td><code>https://api.fireworks.ai</code></td></tr><tr><td><code>https://你的地址/gemini</code></td><td><code>https://generativelanguage.googleapis.com</code></td></tr><tr><td><code>https://你的地址/groq</code></td><td><code>https://api.groq.com/openai</code></td></tr><tr><td><code>https://你的地址/huggingface</code></td><td><code>https://api-inference.huggingface.co</code></td></tr><tr><td><code>https://你的地址/meta</code></td><td><code>https://www.meta.ai/api</code></td></tr><tr><td><code>https://你的地址/novita</code></td><td><code>https://api.novita.ai</code></td></tr><tr><td><code>https://你的地址/nvidia</code></td><td><code>https://integrate.api.nvidia.com</code></td></tr><tr><td><code>https://你的地址/openai</code></td><td><code>https://api.openai.com</code></td></tr><tr><td><code>https://你的地址/openrouter</code></td><td><code>https://openrouter.ai/api</code></td></tr><tr><td><code>https://你的地址/portkey</code></td><td><code>https://api.portkey.ai</code></td></tr><tr><td><code>https://你的地址/telegram</code></td><td><code>https://api.telegram.org</code></td></tr><tr><td><code>https://你的地址/together</code></td><td><code>https://api.together.xyz</code></td></tr><tr><td><code>https://你的地址/xai</code></td><td><code>https://api.x.ai</code></td></tr><tr><td><code>https://你的地址/sambanova</code></td><td><code>https://api.sambanova.ai</code></td></tr><tr><td><code>https://你的地址/mistral</code></td><td><code>https://api.mistral.ai</code></td></tr></tbody></table></section><section><h3>参考项目<a href="#参考项目"><span>#</span></a></h3><a href="https://github.com/PublicAffairs/openai-gemini" target="_blank"><div><div><div><div></div><div>PublicAffairs</div></div><div>/</div><div>openai-gemini</div></div><div></div></div><div>✦ Gemini ➜ OpenAI API proxy. Serverless!</div><div><div>—</div><div>—</div><div>MIT</div><span>JavaScript</span></div></a></section></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.wakaplus.com/posts/gemini-openai/</id>
      <title type="text">Gemini的OpenAI API兼容性</title>
      <published>2025-04-08T00:00:00.000Z</published>
      <updated>2025-04-08T00:00:00.000Z</updated>
      <author><name>waka</name></author>
      <link rel="alternate" href="https://blog.wakaplus.com/posts/gemini-openai/"/>
      <summary type="text">gemini添加了对openai api的兼容性支持，原生支持openai api格式的调用</summary>
      <content type="html"><![CDATA[<section><h2>Gemini 的 OpenAI API 兼容性支持详解<a href="#gemini-的-openai-api-兼容性支持详解"><span>#</span></a></h2><p>Google Gemini 模型现已 <strong>原生支持 OpenAI API 格式的调用方式</strong>，这意味着你可以在保留现有调用逻辑的基础上，几乎零改动地将模型切换至 Gemini。</p><p>借助这一新特性，开发者可以直接通过 OpenAI 官方库（支持 Python 和 TypeScript/JavaScript）或 REST API 来接入 Gemini，只需 <strong>修改 base_url</strong> 并使用你的 Gemini API 密钥，即可完成无缝迁移。</p><p>如果你之前未使用过 OpenAI SDK，也可以直接调用 Gemini API，无需强依赖任何库。</p><hr /><section><h3>如何接入 Gemini 的 OpenAI API 接口<a href="#如何接入-gemini-的-openai-api-接口"><span>#</span></a></h3><p>要使用兼容模式，只需将原本的 <code>base_url</code> 修改为以下地址：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>base_url </span><span>=</span><span> </span></span><span>"https://generativelanguage.googleapis.com/v1beta/openai/"</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>同时，确保你的请求头中包含有效的 Gemini API 密钥。</p><p>这样，你便可以通过与 OpenAI 接口完全一致的方式访问 Gemini 模型。</p><hr /></section><section><h3>当前支持的功能列表<a href="#当前支持的功能列表"><span>#</span></a></h3><p>Gemini 的 OpenAI 接口目前支持以下主要功能，涵盖多模态、生成、理解、结构化输出等核心能力：</p>

<table><thead><tr><th>功能（英文）</th><th>功能（中文）</th></tr></thead><tbody><tr><td>List models</td><td>列出模型列表</td></tr><tr><td>Retrieve a model</td><td>获取指定模型信息</td></tr><tr><td>Streaming</td><td>流式输出</td></tr><tr><td>Function calling</td><td>函数调用</td></tr><tr><td>Image understanding</td><td>图像理解</td></tr><tr><td>Generate an image</td><td>图像生成</td></tr><tr><td>Audio understanding</td><td>音频理解</td></tr><tr><td>Structured output</td><td>结构化输出</td></tr><tr><td>Embeddings</td><td>向量嵌入生成</td></tr></tbody></table><hr /></section><section><h3>使用建议<a href="#使用建议"><span>#</span></a></h3><ul>
<li><strong>已有 OpenAI 接口代码？</strong> 直接更新 <code>base_url</code> 和密钥即可快速接入 Gemini。</li>
<li><strong>尚未接入 OpenAI SDK？</strong> 推荐直接使用 Gemini 官方 API 接口，文档清晰，功能全面。</li>
</ul><hr /></section><section><h3>官方文档链接<a href="#官方文档链接"><span>#</span></a></h3><p>更多详细信息可参考 Gemini 官方文档：</p><p>👉 <a href="https://ai.google.dev/gemini-api/docs/openai" target="_blank">Gemini OpenAI 兼容接口说明</a></p></section></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.wakaplus.com/posts/bitwarden-deduplicator/</id>
      <title type="text">Bitwarden 密码去重</title>
      <published>2025-04-04T00:00:00.000Z</published>
      <updated>2025-04-04T00:00:00.000Z</updated>
      <author><name>waka</name></author>
      <link rel="alternate" href="https://blog.wakaplus.com/posts/bitwarden-deduplicator/"/>
      <summary type="text">使用Python脚本给Bitwarden 密码去重</summary>
      <content type="html"><![CDATA[<section><h2>前言<a href="#前言"><span>#</span></a></h2><p>Bitwarden 是一个非常好的密码管理工具，但是它在导入密码时，如果多次导入，会将重复的密码导入多遍，导致密码库中的密码重复。所以我编写了一个 Python 脚本来去重。通过bitwarden导出的csv文件去重后再重新导入。</p></section>
<section><h2>项目地址<a href="#项目地址"><span>#</span></a></h2><a href="https://github.com/niqifan007/bitwarden_deduplicator" target="_blank"><div><div><div><div></div><div>niqifan007</div></div><div>/</div><div>bitwarden_deduplicator</div></div><div></div></div><div>Bitwarden 密码去重工具</div><div><div>—</div><div>—</div><div>MIT</div><span>Python</span></div></a></section>
<section><h2>注意事项<a href="#注意事项"><span>#</span></a></h2><ol>
<li>在执行脚本前，请确保已经备份了 Bitwarden 密码库
<figure><img src="https://i.111666.best/image/oU2tXmuyIYLP72u204jnDx.png" alt="image" /><figcaption>image</figcaption></figure></li>
<li>这个备份不包括密码库中身份和信用卡信息等，所以当去重后，需要重新导入身份和信用卡信息
<figure><img src="https://i.111666.best/image/FZxuROYHoLhra8dtg1y1sr.png" alt="image" /><figcaption>image</figcaption></figure></li>
<li>需要先清空bitwarden的密码库再导入去重后的csv密码文件</li>
</ol></section>
<section><h2>使用方法<a href="#使用方法"><span>#</span></a></h2><section><h3>克隆仓库<a href="#克隆仓库"><span>#</span></a></h3><p>git clone <a href="https://github.com/niqifan007/bitwarden_deduplicator.git" target="_blank">https://github.com/niqifan007/bitwarden_deduplicator.git</a>
cd bitwarden_deduplicator</p></section><section><h3>安装依赖<a href="#安装依赖"><span>#</span></a></h3><p>pip install -r requirements.txt</p></section><section><h3>运行脚本<a href="#运行脚本"><span>#</span></a></h3><p>python bitwarden_deduplicator.py <strong>bitwarden导出的密码库.csv</strong> [-o 输出文件.csv(可选参数)]</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span># 直接启动交互式模式</span></div></div><div><div><div>2</div></div><div><span>python</span><span> </span><span>bitwarden_deduplicator.py</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span># 或者通过-i参数启动交互式模式</span></div></div><div><div><div>5</div></div><div><span>python</span><span> </span><span>bitwarden_deduplicator.py</span><span> </span><span>-i</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span># 基本用法</span></div></div><div><div><div>8</div></div><div><span>python</span><span> </span><span>bitwarden_deduplicator.py</span><span> </span><span>bitwarden_export.csv</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span># 使用域名级别去重</span></div></div><div><div><div>11</div></div><div><span>python</span><span> </span><span>bitwarden_deduplicator.py</span><span> </span><span>bitwarden_export.csv</span><span> </span><span>-d</span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span># 指定输出文件</span></div></div><div><div><div>14</div></div><div><span>python</span><span> </span><span>bitwarden_deduplicator.py</span><span> </span><span>bitwarden_export.csv</span><span> </span><span>-o</span><span> </span><span>cleaned_export.csv</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.wakaplus.com/posts/windsurf/</id>
      <title type="text">Windsurf 使用指南</title>
      <published>2025-03-29T00:00:00.000Z</published>
      <updated>2025-03-29T00:00:00.000Z</updated>
      <author><name>waka</name></author>
      <link rel="alternate" href="https://blog.wakaplus.com/posts/windsurf/"/>
      <summary type="text">Windsurf使用小技巧</summary>
      <content type="html"><![CDATA[<section><h2>Windsurf 使用 VSCode 插件仓库安装更新插件<a href="#windsurf-使用-vscode-插件仓库安装更新插件"><span>#</span></a></h2><p>默认是使用 openvsx 开源插件仓库，里面的插件版本有些很老</p><ol>
<li>
<p>打开 Windsurf settings</p>
</li>
<li>
<p>修改如图中设置
</p><figure><img src="https://i.111666.best/image/QyOh8d3pmTE2B6t9CSCnBi.png" alt="image" /><figcaption>image</figcaption></figure>
Gallery: <a href="https://marketplace.visualstudio.com/_apis/public/gallery" target="_blank">https://marketplace.visualstudio.com/_apis/public/gallery</a><p></p>
<p>Items: <a href="https://marketplace.visualstudio.com/items" target="_blank">https://marketplace.visualstudio.com/items</a></p>
</li>
<li>
<p>重启 Windsurf</p>
</li>
</ol></section>
<section><h2>Windsurf Cascade 使用中文回复<a href="#windsurf-cascade-使用中文回复"><span>#</span></a></h2><ol>
<li>打开 Windsurf settings</li>
<li>找到 <code>Memories and Rules</code> 项，点击 <code>Global rules</code>的修改按钮 <code>Edit global rules</code>，在里面输入: <strong>Speak to me in Chinese</strong>
<figure><img src="https://i.111666.best/image/VAaWb6ewestaeb4JVS4KAW.png" alt="image" /><figcaption>image</figcaption></figure>
<figure><img src="https://i.111666.best/image/iiCm9esymY8EX1ajWxOx9o.png" alt="image" /><figcaption>image</figcaption></figure></li>
<li>保存md配置文件，重启 Windsurf</li>
</ol></section>
<section><h2>Windsurf 快捷键<a href="#windsurf-快捷键"><span>#</span></a></h2>

<table><thead><tr><th>Windows</th><th>Mac</th><th>Description</th></tr></thead><tbody><tr><td>Ctrl + I</td><td>Cmd + I</td><td>打开命令窗口。</td></tr><tr><td>Ctrl + L</td><td>Cmd + L</td><td>打开/关闭Cascade面板。</td></tr></tbody></table></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.wakaplus.com/posts/webdav/</id>
      <title type="text">使用docker在Linux上搭建Webdav服务端</title>
      <published>2025-01-19T00:00:00.000Z</published>
      <updated>2025-01-19T00:00:00.000Z</updated>
      <author><name>waka</name></author>
      <link rel="alternate" href="https://blog.wakaplus.com/posts/webdav/"/>
      <summary type="text">WebDAV 服务器搭建教程：使用 Docker 快速部署文件共享服务</summary>
      <content type="html"><![CDATA[<section><h1>WebDAV 服务器搭建教程：使用 Docker 快速部署文件共享服务<a href="#webdav-服务器搭建教程使用-docker-快速部署文件共享服务"><span>#</span></a></h1><section><h2>前言<a href="#前言"><span>#</span></a></h2><p>WebDAV (Web Distributed Authoring and Versioning) 是 HTTP 协议的扩展，允许客户端对 Web 服务器上的内容进行在线编辑和管理。本教程将介绍如何使用 Docker 快速搭建一个安全的 WebDAV 服务器。</p></section><section><h2>项目地址<a href="#项目地址"><span>#</span></a></h2><a href="https://github.com/hacdias/webdav" target="_blank"><div><div><div><div></div><div>hacdias</div></div><div>/</div><div>webdav</div></div><div></div></div><div>A simple and standalone WebDAV server.</div><div><div>—</div><div>—</div><div>MIT</div><span>Go</span></div></a></section><section><h2>环境要求<a href="#环境要求"><span>#</span></a></h2><ul>
<li>Docker 环境</li>
<li>一台 Linux/Windows/MacOS 服务器或个人电脑</li>
</ul></section><section><h2>步骤一：创建配置文件<a href="#步骤一创建配置文件"><span>#</span></a></h2><p>首先创建一个工作目录并进入：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>mkdir</span><span> </span><span>webdav</span></div></div><div><div><div>2</div></div><div><span>cd</span><span> </span><span>webdav</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>创建配置文件 <code>config.yml</code>：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span># 服务器地址和端口配置</span></div></div><div><div><div>2</div></div><div><span>address</span><span>: </span><span>0.0.0.0</span></div></div><div><div><div>3</div></div><div><span>port</span><span>: </span><span>6065</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span># 基础配置</span></div></div><div><div><div>6</div></div><div><span>tls</span><span>: </span><span>false</span></div></div><div><div><div>7</div></div><div><span>prefix</span><span>: </span><span>/</span></div></div><div><div><div>8</div></div><div><span>debug</span><span>: </span><span>false</span></div></div><div><div><div>9</div></div><div><span>noSniff</span><span>: </span><span>false</span></div></div><div><div><div>10</div></div><div><span>behindProxy</span><span>: </span><span>false</span></div></div><div><div><div>11</div></div><div><span>directory</span><span>: </span><span>.</span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span># 权限配置（完整读写权限）</span></div></div><div><div><div>14</div></div><div><span>permissions</span><span>: </span><span>CRUD</span></div></div><div><div><div>15</div></div><div>
</div></div><div><div><div>16</div></div><div><span># CORS配置（允许网页访问）</span></div></div><div><div><div>17</div></div><div><span>cors</span><span>:</span></div></div><div><div><div>18</div></div><div><span>  </span><span>enabled</span><span>: </span><span>true</span></div></div><div><div><div>19</div></div><div><span>  </span><span>credentials</span><span>: </span><span>true</span></div></div><div><div><div>20</div></div><div><span>  </span><span>allowed_headers</span><span>:</span></div></div><div><div><div>21</div></div><div><span><span>    </span></span><span>- </span><span>Depth</span></div></div><div><div><div>22</div></div><div><span><span>    </span></span><span>- </span><span>Authorization</span></div></div><div><div><div>23</div></div><div><span><span>    </span></span><span>- </span><span>Content-Type</span></div></div><div><div><div>24</div></div><div><span><span>    </span></span><span>- </span><span>Content-Length</span></div></div><div><div><div>25</div></div><div><span>  </span><span>allowed_hosts</span><span>:</span></div></div><div><div><div>26</div></div><div><span><span>    </span></span><span>- </span><span>"*"</span></div></div><div><div><div>27</div></div><div><span>  </span><span>allowed_methods</span><span>:</span></div></div><div><div><div>28</div></div><div><span><span>    </span></span><span>- </span><span>GET</span></div></div><div><div><div>29</div></div><div><span><span>    </span></span><span>- </span><span>PUT</span></div></div><div><div><div>30</div></div><div><span><span>    </span></span><span>- </span><span>POST</span></div></div><div><div><div>31</div></div><div><span><span>    </span></span><span>- </span><span>DELETE</span></div></div><div><div><div>32</div></div><div><span><span>    </span></span><span>- </span><span>PROPFIND</span></div></div><div><div><div>33</div></div><div><span><span>    </span></span><span>- </span><span>MKCOL</span></div></div><div><div><div>34</div></div><div><span><span>    </span></span><span>- </span><span>COPY</span></div></div><div><div><div>35</div></div><div><span><span>    </span></span><span>- </span><span>MOVE</span></div></div><div><div><div>36</div></div><div><span>  </span><span>exposed_headers</span><span>:</span></div></div><div><div><div>37</div></div><div><span><span>    </span></span><span>- </span><span>Content-Length</span></div></div><div><div><div>38</div></div><div><span><span>    </span></span><span>- </span><span>Content-Range</span></div></div><div><div><div>39</div></div><div>
</div></div><div><div><div>40</div></div><div><span># 用户认证配置</span></div></div><div><div><div>41</div></div><div><span>users</span><span>:</span></div></div><div><div><div>42</div></div><div><span><span>  </span></span><span>- </span><span>username</span><span>: </span><span>你的用户名</span></div></div><div><div><div>43</div></div><div><span>    </span><span>password</span><span>: </span><span>你的密码</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section><section><h2>步骤二：创建数据目录<a href="#步骤二创建数据目录"><span>#</span></a></h2><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>mkdir</span><span> </span><span>data</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>步骤三：启动 Docker 容器<a href="#步骤三启动-docker-容器"><span>#</span></a></h2><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>docker</span><span> </span><span>run</span><span> </span><span>\</span></div></div><div><div><div>2</div></div><div><span>  </span><span>-p</span><span> </span><span>6065:6065</span><span> </span><span>\</span></div></div><div><div><div>3</div></div><div><span>  </span><span>-v</span><span> $(</span><span>pwd</span><span>)</span><span>/config.yml:/config.yml:ro</span><span> </span><span>\</span></div></div><div><div><div>4</div></div><div><span>  </span><span>-v</span><span> $(</span><span>pwd</span><span>)</span><span>/data:/data</span><span> </span><span>\</span></div></div><div><div><div>5</div></div><div><span>  </span><span>ghcr.io/hacdias/webdav</span><span> </span><span>-c</span><span> </span><span>/config.yml</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>配置说明<a href="#配置说明"><span>#</span></a></h2><section><h3>1. 端口配置<a href="#1-端口配置"><span>#</span></a></h3><ul>
<li><code>address: 0.0.0.0</code> 允许所有网络接口访问</li>
<li><code>port: 6065</code> 服务监听端口</li>
</ul></section><section><h3>2. 权限设置<a href="#2-权限设置"><span>#</span></a></h3><p>权限类型包括：</p><ul>
<li>C：Create（创建）</li>
<li>R：Read（读取）</li>
<li>U：Update（更新）</li>
<li>D：Delete（删除）</li>
</ul></section><section><h3>3. CORS 配置<a href="#3-cors-配置"><span>#</span></a></h3><ul>
<li><code>enabled: true</code> 启用跨域访问</li>
<li><code>allowed_hosts: "*"</code> 允许所有域名访问</li>
<li><code>allowed_methods</code> 配置允许的 HTTP 方法</li>
</ul></section><section><h3>4. 安全性考虑<a href="#4-安全性考虑"><span>#</span></a></h3><ul>
<li>建议修改默认用户名和密码</li>
<li>如果不需要网页访问，可以禁用 CORS</li>
<li>生产环境建议启用 TLS 加密</li>
</ul></section></section><section><h2>进阶配置<a href="#进阶配置"><span>#</span></a></h2><section><h3>反向代理配置<a href="#反向代理配置"><span>#</span></a></h3><p>如果你使用 Nginx 作为反向代理，需要正确转发头信息以避免 502 错误：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>location</span><span><span> </span><span>/ </span><span>{</span></span></div></div><div><div><div>2</div></div><div><span> <span> </span></span><span>proxy_pass </span><span>http://127.0.0.1:6065;</span></div></div><div><div><div>3</div></div><div><span> <span> </span></span><span>proxy_set_header </span><span>X-Real-IP $</span><span>remote_addr</span><span>;</span></div></div><div><div><div>4</div></div><div><span> <span> </span></span><span>proxy_set_header </span><span>REMOTE-HOST $</span><span>remote_addr</span><span>;</span></div></div><div><div><div>5</div></div><div><span> <span> </span></span><span>proxy_set_header </span><span>X-Forwarded-For $</span><span>proxy_add_x_forwarded_for</span><span>;</span></div></div><div><div><div>6</div></div><div><span> <span> </span></span><span>proxy_set_header </span><span>Host $</span><span>host</span><span>;</span></div></div><div><div><div>7</div></div><div><span> <span> </span></span><span>proxy_redirect </span><span>off</span><span>;</span></div></div><div><div><div>8</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section><section><h2>访问方式<a href="#访问方式"><span>#</span></a></h2><section><h3>1. 网页访问<a href="#1-网页访问"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>http://服务器IP:6065</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>2. WebDAV 客户端<a href="#2-webdav-客户端"><span>#</span></a></h3><ul>
<li>MacOS: Cyberduck</li>
</ul></section></section><section><h2>常见问题<a href="#常见问题"><span>#</span></a></h2><section><h3>1. 403 权限错误<a href="#1-403-权限错误"><span>#</span></a></h3><p>检查：</p><ul>
<li>permissions 是否配置为 CRUD</li>
<li>用户权限是否正确</li>
<li>目录权限是否正确</li>
</ul></section><section><h3>2. CORS 错误<a href="#2-cors-错误"><span>#</span></a></h3><p>检查：</p><ul>
<li>allowed_hosts 配置</li>
<li>allowed_methods 是否包含需要的方法</li>
<li>allowed_headers 是否完整</li>
</ul></section></section><section><h2>总结<a href="#总结"><span>#</span></a></h2><p>通过 Docker 部署 WebDAV 服务器具有以下优势：</p><ol>
<li>部署简单快速</li>
<li>配置灵活</li>
<li>易于维护</li>
<li>支持多平台</li>
</ol></section><section><h2>附录：官方配置文件示例<a href="#附录官方配置文件示例"><span>#</span></a></h2><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span># 服务器地址和端口配置</span></div></div><div><div><div>2</div></div><div><span>address</span><span>: </span><span>0.0.0.0</span></div></div><div><div><div>3</div></div><div><span>port</span><span>: </span><span>6065</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span># TLS 相关设置（如果你想直接启用 TLS）</span></div></div><div><div><div>6</div></div><div><span>tls</span><span>: </span><span>false</span></div></div><div><div><div>7</div></div><div><span>cert</span><span>: </span><span>cert.pem</span></div></div><div><div><div>8</div></div><div><span>key</span><span>: </span><span>key.pem</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span># WebDAV 路径的前缀。默认为 '/'</span></div></div><div><div><div>11</div></div><div><span>prefix</span><span>: </span><span>/</span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span># 启用或禁用调试日志。默认为 'false'</span></div></div><div><div><div>14</div></div><div><span>debug</span><span>: </span><span>false</span></div></div><div><div><div>15</div></div><div>
</div></div><div><div><div>16</div></div><div><span># 禁用文件内容类型嗅探。默认为 'false'</span></div></div><div><div><div>17</div></div><div><span>noSniff</span><span>: </span><span>false</span></div></div><div><div><div>18</div></div><div>
</div></div><div><div><div>19</div></div><div><span># 服务器是否运行在受信任的代理后面。当设置为 true 时，</span></div></div><div><div><div>20</div></div><div><span># 将使用 X-Forwarded-For 头部来记录访问尝试的远程地址（如果可用）</span></div></div><div><div><div>21</div></div><div><span>behindProxy</span><span>: </span><span>false</span></div></div><div><div><div>22</div></div><div>
</div></div><div><div><div>23</div></div><div><span># 用户连接时可以访问的目录。</span></div></div><div><div><div>24</div></div><div><span># 除非用户有自己的 'directory' 定义，否则将使用此目录。</span></div></div><div><div><div>25</div></div><div><span># 默认为 '.'（当前目录）</span></div></div><div><div><div>26</div></div><div><span>directory</span><span>: </span><span>.</span></div></div><div><div><div>27</div></div><div>
</div></div><div><div><div>28</div></div><div><span># 用户的默认权限。这是一个不区分大小写的选项。可能的权限包括：</span></div></div><div><div><div>29</div></div><div><span># C（创建）、R（读取）、U（更新）、D（删除）。</span></div></div><div><div><div>30</div></div><div><span># 你可以组合多个权限。例如，要允许读取和创建，设置为 "RC"。默认为 "R"</span></div></div><div><div><div>31</div></div><div><span>permissions</span><span>: </span><span>R</span></div></div><div><div><div>32</div></div><div>
</div></div><div><div><div>33</div></div><div><span># 用户的默认权限规则。默认为空。规则从后向前应用，</span></div></div><div><div><div>34</div></div><div><span># 即从末尾开始匹配请求的第一条规则将被应用</span></div></div><div><div><div>35</div></div><div><span>rules</span><span>: []</span></div></div><div><div><div>36</div></div><div>
</div></div><div><div><div>37</div></div><div><span># 重新定义用户规则的行为。可以是：</span></div></div><div><div><div>38</div></div><div><span># - overwrite: 当用户有规则定义时，这些规则将覆盖任何已定义的全局规则。</span></div></div><div><div><div>39</div></div><div><span>#   即全局规则不适用于该用户。</span></div></div><div><div><div>40</div></div><div><span># - append: 当用户有规则定义时，这些规则将附加到已定义的全局规则之后。</span></div></div><div><div><div>41</div></div><div><span>#   即对于该用户，首先检查其特定规则，然后是全局规则。</span></div></div><div><div><div>42</div></div><div><span># 默认为 'overwrite'</span></div></div><div><div><div>43</div></div><div><span>rulesBehavior</span><span>: </span><span>overwrite</span></div></div><div><div><div>44</div></div><div>
</div></div><div><div><div>45</div></div><div><span># 日志配置</span></div></div><div><div><div>46</div></div><div><span>log</span><span>:</span></div></div><div><div><div>47</div></div><div><span>  </span><span># 日志格式（'console' 或 'json'）。默认为 'console'</span></div></div><div><div><div>48</div></div><div><span>  </span><span>format</span><span>: </span><span>console</span></div></div><div><div><div>49</div></div><div><span>  </span><span># 启用或禁用颜色。默认为 'true'。仅在 format 为 'console' 时适用</span></div></div><div><div><div>50</div></div><div><span>  </span><span>colors</span><span>: </span><span>true</span></div></div><div><div><div>51</div></div><div><span>  </span><span># 日志输出。可以有多个输出。默认仅为 'stderr'</span></div></div><div><div><div>52</div></div><div><span>  </span><span>outputs</span><span>:</span></div></div><div><div><div>53</div></div><div><span><span>    </span></span><span>- </span><span>stderr</span></div></div><div><div><div>54</div></div><div>
</div></div><div><div><div>55</div></div><div><span># CORS 配置</span></div></div><div><div><div>56</div></div><div><span>cors</span><span>:</span></div></div><div><div><div>57</div></div><div><span>  </span><span># 是否应用 CORS 配置。默认为 'false'</span></div></div><div><div><div>58</div></div><div><span>  </span><span>enabled</span><span>: </span><span>true</span></div></div><div><div><div>59</div></div><div><span>  </span><span>credentials</span><span>: </span><span>true</span></div></div><div><div><div>60</div></div><div><span>  </span><span>allowed_headers</span><span>:</span></div></div><div><div><div>61</div></div><div><span><span>    </span></span><span>- </span><span>Depth</span></div></div><div><div><div>62</div></div><div><span>  </span><span>allowed_hosts</span><span>:</span></div></div><div><div><div>63</div></div><div><span><span>    </span></span><span>- </span><span>http://localhost:8080</span></div></div><div><div><div>64</div></div><div><span>  </span><span>allowed_methods</span><span>:</span></div></div><div><div><div>65</div></div><div><span><span>    </span></span><span>- </span><span>GET</span></div></div><div><div><div>66</div></div><div><span>  </span><span>exposed_headers</span><span>:</span></div></div><div><div><div>67</div></div><div><span><span>    </span></span><span>- </span><span>Content-Length</span></div></div><div><div><div>68</div></div><div><span><span>    </span></span><span>- </span><span>Content-Range</span></div></div><div><div><div>69</div></div><div>
</div></div><div><div><div>70</div></div><div><span># 用户列表。如果列表为空，则不会有身份验证。</span></div></div><div><div><div>71</div></div><div><span># 否则，将自动配置基本身份验证。</span></div></div><div><div><div>72</div></div><div><span>#</span></div></div><div><div><div>73</div></div><div><span># 如果你将身份验证委托给其他服务，你可以使用基本身份验证代理用户名，</span></div></div><div><div><div>74</div></div><div><span># 然后使用以下选项禁用 webdav 的密码检查：</span></div></div><div><div><div>75</div></div><div><span>#</span></div></div><div><div><div>76</div></div><div><span># noPassword: true</span></div></div><div><div><div>77</div></div><div><span>users</span><span>:</span></div></div><div><div><div>78</div></div><div><span>  </span><span># 示例 'admin' 用户，使用明文密码</span></div></div><div><div><div>79</div></div><div><span><span>  </span></span><span>- </span><span>username</span><span>: </span><span>admin</span></div></div><div><div><div>80</div></div><div><span>    </span><span>password</span><span>: </span><span>admin</span></div></div><div><div><div>81</div></div><div><span>  </span><span># 示例 'john' 用户，使用 bcrypt 加密密码，具有自定义目录。</span></div></div><div><div><div>82</div></div><div><span>  </span><span># 你可以使用 'webdav bcrypt' 命令行工具生成 bcrypt 加密密码</span></div></div><div><div><div>83</div></div><div><span><span>  </span></span><span>- </span><span>username</span><span>: </span><span>john</span></div></div><div><div><div>84</div></div><div><span>    </span><span>password</span><span>: </span><span>"{bcrypt}$2y$10$zEP6oofmXFeHaeMfBNLnP.DO8m.H.Mwhd24/TOX2MWLxAExXi4qgi"</span></div></div><div><div><div>85</div></div><div><span>    </span><span>directory</span><span>: </span><span>/another/path</span></div></div><div><div><div>86</div></div><div><span>  </span><span># 示例用户，其详细信息将从环境变量中获取</span></div></div><div><div><div>87</div></div><div><span><span>  </span></span><span>- </span><span>username</span><span>: </span><span>"{env}ENV_USERNAME"</span></div></div><div><div><div>88</div></div><div><span>    </span><span>password</span><span>: </span><span>"{env}ENV_PASSWORD"</span></div></div><div><div><div>89</div></div><div><span><span>  </span></span><span>- </span><span>username</span><span>: </span><span>basic</span></div></div><div><div><div>90</div></div><div><span>    </span><span>password</span><span>: </span><span>basic</span></div></div><div><div><div>91</div></div><div><span>    </span><span># 覆盖默认权限</span></div></div><div><div><div>92</div></div><div><span>    </span><span>permissions</span><span>: </span><span>CRUD</span></div></div><div><div><div>93</div></div><div><span>    </span><span>rules</span><span>:</span></div></div><div><div><div>94</div></div><div><span>      </span><span># 通过此规则，用户不能访问 /some/files</span></div></div><div><div><div>95</div></div><div><span><span>      </span></span><span>- </span><span>path</span><span>: </span><span>/some/file</span></div></div><div><div><div>96</div></div><div><span>        </span><span>permissions</span><span>: </span><span>none</span></div></div><div><div><div>97</div></div><div><span>      </span><span># 通过此规则，用户可以在 /public/access 中创建、读取、更新和删除</span></div></div><div><div><div>98</div></div><div><span><span>      </span></span><span>- </span><span>path</span><span>: </span><span>/public/access/</span></div></div><div><div><div>99</div></div><div><span>        </span><span>permissions</span><span>: </span><span>CRUD</span></div></div><div><div><div>100</div></div><div><span>      </span><span># 通过此规则，用户可以读取和更新所有以 .js 结尾的文件。使用正则表达式</span></div></div><div><div><div>101</div></div><div><span><span>      </span></span><span>- </span><span>regex</span><span>: </span><span>"^.+.js$"</span></div></div><div><div><div>102</div></div><div><span>        </span><span>permissions</span><span>: </span><span>RU</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.wakaplus.com/posts/aiga-proxy/</id>
      <title type="text">利用deno代理cloudflare ai gateway</title>
      <published>2025-01-11T00:00:00.000Z</published>
      <updated>2025-01-11T00:00:00.000Z</updated>
      <author><name>waka</name></author>
      <link rel="alternate" href="https://blog.wakaplus.com/posts/aiga-proxy/"/>
      <summary type="text">使用deno反代ai gateway，解决地区限制</summary>
      <content type="html"><![CDATA[<section><h2>利用deno代理cloudflare ai gateway<a href="#利用deno代理cloudflare-ai-gateway"><span>#</span></a></h2><p>上一篇文章介绍了如何使用cloudflare ai gateway，但是由于cloudflare ai gateway会透传用户的ip，所以有些地区原本无法访问的ai api还是会无法访问，这时候我们可以使用deno来代理cloudflare ai gateway，这样就可以解决地区限制的问题。</p><section><h3>代码<a href="#代码"><span>#</span></a></h3><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>const</span><span> </span><span>TARGET_HOST</span><span> </span><span>=</span><span> </span><span>"gateway.ai.cloudflare.com"</span><span>;</span></div></div><div><div><div>2</div></div><div><span>const</span><span> </span><span>BASE_PATH</span><span> </span><span>=</span><span> </span><span>"/v1/你自己的标识符/aigate/google-ai-studio"</span><span>;</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>Deno.</span><span>serve</span><span>(</span><span>async</span><span><span> (</span><span>request</span><span>) </span></span><span>=&gt;</span><span> {</span></div></div><div><div><div>5</div></div><div><span>  </span><span>const</span><span> </span><span>url</span><span> </span><span>=</span><span> </span><span>new</span><span> </span><span>URL</span><span>(request.</span><span>url</span><span>);</span></div></div><div><div><div>6</div></div><div><span>  </span><span>const</span><span> </span><span>originalPath</span><span> </span><span>=</span><span> url.</span><span>pathname</span><span>;</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span><span>  </span></span><span>url.</span><span>host</span><span> </span><span>=</span><span> </span><span>TARGET_HOST</span><span>;</span></div></div><div><div><div>9</div></div><div><span><span>  </span></span><span>url.</span><span>pathname</span><span> </span><span>=</span><span> </span><span>BASE_PATH</span><span> </span><span>+</span><span> originalPath;</span></div></div><div><div><div>10</div></div><div><span><span>  </span></span><span>url.</span><span>protocol</span><span> </span><span>=</span><span> </span><span>"https:"</span><span>;</span></div></div><div><div><div>11</div></div><div>
</div></div><div><div><div>12</div></div><div><span>  </span><span>const</span><span> </span><span>newRequest</span><span> </span><span>=</span><span> </span><span>new</span><span> </span><span>Request</span><span>(url.</span><span>toString</span><span>(), {</span></div></div><div><div><div>13</div></div><div><span><span>    </span></span><span>headers</span><span>:</span><span> request.</span><span>headers</span><span>,</span></div></div><div><div><div>14</div></div><div><span><span>    </span></span><span>method</span><span>:</span><span> request.</span><span>method</span><span>,</span></div></div><div><div><div>15</div></div><div><span><span>    </span></span><span>body</span><span>:</span><span> request.</span><span>body</span><span>,</span></div></div><div><div><div>16</div></div><div><span><span>    </span></span><span>redirect</span><span>:</span><span> </span><span>"follow"</span><span>,</span></div></div><div><div><div>17</div></div><div><span><span>  </span></span><span>});</span></div></div><div><div><div>18</div></div><div>
</div></div><div><div><div>19</div></div><div><span>  </span><span>const</span><span> </span><span>response</span><span> </span><span>=</span><span> </span><span>await</span><span> </span><span>fetch</span><span>(newRequest);</span></div></div><div><div><div>20</div></div><div>
</div></div><div><div><div>21</div></div><div><span>  </span><span>// 添加 CORS 头</span></div></div><div><div><div>22</div></div><div><span>  </span><span>const</span><span> </span><span>headers</span><span> </span><span>=</span><span> </span><span>new</span><span> </span><span>Headers</span><span>(response.</span><span>headers</span><span>);</span></div></div><div><div><div>23</div></div><div><span><span>  </span></span><span>headers.</span><span>set</span><span>(</span><span>"Access-Control-Allow-Origin"</span><span>, </span><span>"*"</span><span>);</span></div></div><div><div><div>24</div></div><div><span><span>  </span></span><span>headers.</span><span>set</span><span>(</span><span>"Access-Control-Allow-Methods"</span><span>, </span><span>"*"</span><span>);</span></div></div><div><div><div>25</div></div><div><span><span>  </span></span><span>headers.</span><span>set</span><span>(</span><span>"Access-Control-Allow-Headers"</span><span>, </span><span>"*"</span><span>);</span></div></div><div><div><div>26</div></div><div>
</div></div><div><div><div>27</div></div><div><span>  </span><span>return</span><span> </span><span>new</span><span> </span><span>Response</span><span>(response.</span><span>body</span><span>, {</span></div></div><div><div><div>28</div></div><div><span><span>    </span></span><span>status</span><span>:</span><span> response.</span><span>status</span><span>,</span></div></div><div><div><div>29</div></div><div><span><span>    </span></span><span>headers</span><span>:</span><span> headers,</span></div></div><div><div><div>30</div></div><div><span><span>  </span></span><span>});</span></div></div><div><div><div>31</div></div><div><span>});</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section><section><h3>说明<a href="#说明"><span>#</span></a></h3><p>这个是一个例子，代理了gateway的google-ai-studio，你可以根据自己的需求修改<code>BASE_PATH</code>，这样就可以代理其他的ai gateway了。使用方法和gateway一样，只是地址换成了你的deno服务的地址。</p></section></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.wakaplus.com/posts/vps-bash/</id>
      <title type="text">常用VPS脚本合集</title>
      <published>2025-01-11T00:00:00.000Z</published>
      <updated>2025-01-11T00:00:00.000Z</updated>
      <author><name>waka</name></author>
      <link rel="alternate" href="https://blog.wakaplus.com/posts/vps-bash/"/>
      <summary type="text">常用VPS脚本合集</summary>
      <content type="html"><![CDATA[<section><h2>1、DD重装脚本<a href="#1dd重装脚本"><span>#</span></a></h2><p>史上最强脚本</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>wget</span><span> </span><span>--no-check-certificate</span><span> </span><span>-qO</span><span> </span><span>InstallNET.sh</span><span> </span><span>'https://raw.githubusercontent.com/leitbogioro/Tools/master/Linux_reinstall/InstallNET.sh'</span><span> &amp;&amp; </span><span>chmod</span><span> </span><span>a+x</span><span> </span><span>InstallNET.sh</span><span> &amp;&amp; </span><span>bash</span><span> </span><span>InstallNET.sh</span><span> </span><span>-debian</span><span> </span><span>12</span><span> </span><span>-pwd</span><span> </span><span>'password'</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>萌咖大佬的脚本</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>bash &lt;(wget --</span><span>no</span><span>-check-certificate -qO- </span><span>'https://raw.githubusercontent.com/MoeClub/Note/master/InstallNET.sh'</span><span><span>) </span><span>-d</span><span> 11 -v 64 </span><span>-p</span><span> 密码 -port 端口 -a -firmware</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>beta.gs大佬的脚本</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>wget --</span><span>no</span><span><span>-check-certificate </span><span>-O</span><span> NewReinstall.sh https://raw.githubusercontent.com/fcurrk/reinstall/master/NewReinstall.sh &amp;&amp; </span></span><span>chmod</span><span> a+x NewReinstall.sh &amp;&amp; bash NewReinstall.sh</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>DD windows（使用史上最强DD脚本）</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>bash </span><span>&lt;</span><span>(curl </span><span>-</span><span>sSL </span></span><span>https:</span><span><span>//</span><span>raw.</span></span><span>githubusercontent</span><span>.</span><span>com</span><span><span>/</span><span>leitbogioro</span><span>/</span></span><span>Tools</span><span><span>/</span><span>master</span><span>/</span></span><span>Linux_reinstall</span><span>/</span><span>InstallNET</span><span>.</span><span>sh</span><span><span>) </span><span>-</span><span>windows </span></span><span>10</span><span><span> </span><span>-</span><span>lang </span></span><span>"cn"</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>账户：Administrator 密码：Teddysun.com</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section>
<section><h2>2、综合测试脚本<a href="#2综合测试脚本"><span>#</span></a></h2><p>LemonBench</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>wget</span><span> </span><span>-qO-</span><span> </span><span>https://raw.githubusercontent.com/LemonBench/LemonBench/main/LemonBench.sh</span><span><span> </span><span>|</span><span> </span></span><span>bash</span><span> </span><span>-s</span><span> </span><span>--</span><span> </span><span>--fast</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>融合怪</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>bash &lt;(wget -qO- --</span><span>no</span><span>-check-certificate https://gitlab.com/spiritysdx/za/-/raw/main/ecs.sh)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>NodeBench</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>bash</span><span> </span><span>&lt;(</span><span>curl</span><span> </span><span>-sL</span><span> https://raw.githubusercontent.com/LloydAsp/NodeBench/main/NodeBench.sh)</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section>
<section><h2>3、性能测试<a href="#3性能测试"><span>#</span></a></h2><p>GB6 跑分脚本，附带宽测试：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>curl -sL yabs.sh | bash</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>GB6 剔除带宽测试，因为都是国外节点测试，国内跑没多大意义：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>curl</span><span><span> </span><span>-</span></span><span>sL</span><span> </span><span>yabs</span><span><span>.</span><span>sh</span><span> | </span></span><span>bash</span><span><span> </span><span>-</span></span><span>s</span><span> </span><span>-- -i</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>GB5 跑分脚本，附带宽测试：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>curl -sL yabs.sh | bash -5</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>GB5 剔除带宽测试：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>curl</span><span><span> </span><span>-</span></span><span>sL</span><span> </span><span>yabs</span><span><span>.</span><span>sh</span><span> | </span></span><span>bash</span><span><span> </span><span>-</span></span><span>s</span><span> </span><span>-- -i -5</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section>
<section><h2>4、流媒体及IP质量测试<a href="#4流媒体及ip质量测试"><span>#</span></a></h2><p>最常用版本</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>bash &lt;(curl -L -s check</span><span>.unlock.media</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>原生检测脚本</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>bash &lt;(curl -sL Media</span><span>.Check.Place</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>准确度最高</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>bash</span><span> </span><span>&lt;(</span><span>curl</span><span> </span><span>-L</span><span> </span><span>-s</span><span> https://github.com/1-stream/RegionRestrictionCheck/raw/main/check.sh)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>IP质量体检脚本</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>bash &lt;(curl -sL IP</span><span>.Check.Place</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section>
<section><h2>5、测速脚本<a href="#5测速脚本"><span>#</span></a></h2><p>Speedtest</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>bash</span><span> </span><span>&lt;(</span><span>curl</span><span> </span><span>-sL</span><span> bash.icu/speedtest)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>Taier</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>bash</span><span> </span><span>&lt;(</span><span>curl</span><span> </span><span>-sL</span><span> res.yserver.ink/taier.sh)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>hyperspeed</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>bash</span><span> </span><span>&lt;(</span><span>curl</span><span> </span><span>-Lso-</span><span> https://bench.im/hyperspeed)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>全球测速</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>curl -sL network-speed.xyz | bash</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section>
<section><h2>6、回程测试<a href="#6回程测试"><span>#</span></a></h2><p>直接显示回程（小白用这个）</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>curl</span><span> </span><span>https://raw.githubusercontent.com/ludashi2020/backtrace/main/install.sh</span><span> </span><span>-sSf</span><span><span> </span><span>|</span><span> </span></span><span>sh</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>回程详细测试（推荐）</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>wget</span><span> </span><span>-N</span><span> </span><span>--no-check-certificate</span><span> </span><span>https://raw.githubusercontent.com/Chennhaoo/Shell_Bash/master/AutoTrace.sh</span><span> &amp;&amp; </span><span>chmod</span><span> </span><span>+x</span><span> </span><span>AutoTrace.sh</span><span> &amp;&amp; </span><span>bash</span><span> </span><span>AutoTrace.sh</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>wget </span><span>https:</span><span><span>//</span><span>ghproxy.</span></span><span>com</span><span>/</span><span>https:</span><span><span>//</span><span>raw.</span></span><span>githubusercontent</span><span>.</span><span>com</span><span><span>/</span><span>vpsxb</span><span>/</span><span>testrace</span><span>/</span><span>main</span><span>/</span><span>testrace.</span></span><span>sh</span><span><span> </span><span>-</span></span><span>O</span><span> testrace.</span><span>sh</span><span><span> </span><span>&amp;&amp;</span><span> bash testrace.</span></span><span>sh</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section>
<section><h2>7、功能脚本<a href="#7功能脚本"><span>#</span></a></h2><p>添加SWAP</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>wget </span><span>https:</span><span><span>//</span><span>www.</span></span><span>moerats</span><span>.</span><span>com</span><span><span>/</span><span>usr</span><span>/</span><span>shell</span><span>/</span><span>swap.</span></span><span>sh</span><span><span> </span><span>&amp;&amp;</span><span> bash swap.</span></span><span>sh</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>Fail2ban</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>wget</span><span> </span><span>--no-check-certificate</span><span> </span><span>https://raw.githubusercontent.com/FunctionClub/Fail2ban/master/fail2ban.sh</span><span> &amp;&amp; </span><span>bash</span><span> </span><span>fail2ban.sh</span><span><span> </span><span>2&gt;&amp;1</span><span> </span><span>|</span><span> </span></span><span>tee</span><span> </span><span>fail2ban.log</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>一键开启BBR，适用于较新的Debian、Ubuntu</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>echo</span><span> </span><span>"net.core.default_qdisc=fq"</span><span><span> </span><span>&gt;&gt;</span><span> </span></span><span>/etc/sysctl.conf</span><span> </span><span>echo</span><span> </span><span>"net.ipv4.tcp_congestion_control=bbr"</span><span><span> </span><span>&gt;&gt;</span><span> </span></span><span>/etc/sysctl.conf</span><span> </span><span>sysctl</span><span> </span><span>-p</span><span> </span><span>sysctl</span><span> </span><span>net.ipv4.tcp_available_congestion_control</span><span> </span><span>lsmod</span><span><span> </span><span>|</span><span> </span></span><span>grep</span><span> </span><span>bbr</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>多功能BBR安装脚本</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>wget</span><span> </span><span>-N</span><span> </span><span>--no-check-certificate</span><span> </span><span>"https://gist.github.com/zeruns/a0ec603f20d1b86de6a774a8ba27588f/raw/4f9957ae23f5efb2bb7c57a198ae2cffebfb1c56/tcp.sh"</span><span> &amp;&amp; </span><span>chmod</span><span> </span><span>+x</span><span> </span><span>tcp.sh</span><span> &amp;&amp; </span><span>./tcp.sh</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>锐速/BBRPLUS/BBR2/BBR3</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>wget</span><span> </span><span>-O</span><span> </span><span>tcpx.sh</span><span> </span><span>"https://github.com/ylx2016/Linux-NetSpeed/raw/master/tcpx.sh"</span><span> &amp;&amp; </span><span>chmod</span><span> </span><span>+x</span><span> </span><span>tcpx.sh</span><span> &amp;&amp; </span><span>./tcpx.sh</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>TCP窗口调优</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>wget</span><span> </span><span>http://sh.nekoneko.cloud/tools.sh</span><span> </span><span>-O</span><span> </span><span>tools.sh</span><span> &amp;&amp; </span><span>bash</span><span> </span><span>tools.sh</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>添加warp</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>wget -N https://gitlab.com/fscarmen/warp/-/raw/main/menu.sh &amp;&amp; bash menu.sh [option] [lisence/url/token]</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>25端口开放测试</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>telnet smtp.aol.com 25</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section>
<section><h2>8、一键安装常用环境及软件<a href="#8一键安装常用环境及软件"><span>#</span></a></h2><p>docker</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>curl</span><span> </span><span>-sSL</span><span> </span><span>https://get.daocloud.io/docker</span><span><span> </span><span>|</span><span> </span></span><span>sh</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>Python</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>curl</span><span> </span><span>-O</span><span> </span><span>https://raw.githubusercontent.com/lx969788249/lxspacepy/master/pyinstall.sh</span><span> &amp;&amp; </span><span>chmod</span><span> </span><span>+x</span><span> </span><span>pyinstall.sh</span><span> &amp;&amp; </span><span>./pyinstall.sh</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>iperf3</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>apt install iperf3</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>realm</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>bash</span><span> </span><span>&lt;(</span><span>curl</span><span> </span><span>-L</span><span> https://raw.githubusercontent.com/zhouh047/realm-oneclick-install/main/realm.sh)</span><span> </span><span>-i</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>gost</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>wget</span><span> </span><span>--no-check-certificate</span><span> </span><span>-O</span><span> </span><span>gost.sh</span><span> </span><span>https://raw.githubusercontent.com/qqrrooty/EZgost/main/gost.sh</span><span> &amp;&amp; </span><span>chmod</span><span> </span><span>+x</span><span> </span><span>gost.sh</span><span> &amp;&amp; </span><span>./gost.sh</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>极光面板</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>bash</span><span> </span><span>&lt;(</span><span>curl</span><span> </span><span>-fsSL</span><span> https://raw.githubusercontent.com/Aurora-Admin-Panel/deploy/main/install.sh)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>哪吒监控</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>curl</span><span> </span><span>-L</span><span> </span><span>https://raw.githubusercontent.com/naiba/nezha/master/script/install.sh</span><span> </span><span>-o</span><span> </span><span>nezha.sh</span><span> &amp;&amp; </span><span>chmod</span><span> </span><span>+x</span><span> </span><span>nezha.sh</span><span> &amp;&amp; </span><span>sudo</span><span> </span><span>./nezha.sh</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>WARP</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>wget -N https://gitlab.com/fscarmen/warp/-/raw/main/menu.sh &amp;&amp; bash menu.sh</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>Aria2</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>wget</span><span> </span><span>-N</span><span> </span><span>git.io/aria2.sh</span><span> &amp;&amp; </span><span>chmod</span><span> </span><span>+x</span><span> </span><span>aria2.sh</span><span> &amp;&amp; </span><span>./aria2.sh</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>宝塔</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>wget</span><span> </span><span>-O</span><span> </span><span>install.sh</span><span> </span><span>http://v7.hostcli.com/install/install-ubuntu_6.0.sh</span><span> &amp;&amp; </span><span>sudo</span><span> </span><span>bash</span><span> </span><span>install.sh</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>PVE虚拟化</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>bash </span><span>&lt;</span><span>(wget </span><span>-</span><span>qO</span><span>-</span><span> </span><span>--</span><span>no</span><span>-</span><span>check</span><span>-</span><span>certificate </span></span><span>https:</span><span><span>//</span><span>raw.</span></span><span>githubusercontent</span><span>.</span><span>com</span><span><span>/</span><span>oneclickvirt</span><span>/</span><span>pve</span><span>/</span><span>main</span><span>/</span><span>scripts</span><span>/</span><span>build_backend.</span></span><span>sh</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>Argox</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>bash</span><span> </span><span>&lt;(</span><span>wget</span><span> </span><span>-qO-</span><span> https://raw.githubusercontent.com/fscarmen/argox/main/argox.sh)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>1panel</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>curl</span><span> </span><span>-sSL</span><span> </span><span>https://resource.fit2cloud.com/1panel/package/quick_start.sh</span><span> </span><span>-o</span><span> </span><span>quick_start.sh</span><span> &amp;&amp; </span><span>sudo</span><span> </span><span>bash</span><span> </span><span>quick_start.sh</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section>
<section><h2>9、综合功能脚本<a href="#9综合功能脚本"><span>#</span></a></h2><p>科技lion</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>apt update -y </span><span>&amp;&amp;</span><span> apt install -y curl bash &lt;(curl -sL kejilion</span><span>.sh</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>SKY-BOX</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>wget</span><span> </span><span>-O</span><span> </span><span>box.sh</span><span> </span><span>https://raw.githubusercontent.com/BlueSkyXN/SKY-BOX/main/box.sh</span><span> &amp;&amp; </span><span>chmod</span><span> </span><span>+x</span><span> </span><span>box.sh</span><span> &amp;&amp; </span><span>clear</span><span> &amp;&amp; </span><span>./box.sh</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.wakaplus.com/posts/aigate/</id>
      <title type="text">使用Cloudflare AI Gateway监控、控制和优化 AI 应用</title>
      <published>2025-01-05T00:00:00.000Z</published>
      <updated>2025-01-05T00:00:00.000Z</updated>
      <author><name>waka</name></author>
      <link rel="alternate" href="https://blog.wakaplus.com/posts/aigate/"/>
      <summary type="text">Cloudflare AI Gateway 为你的 AI 应用提供集中管理和控制。只需一行代码即可连接您的应用，监控使用情况、成本和错误。</summary>
      <content type="html"><![CDATA[<section><h2>使用 Cloudflare AI Gateway 监控、控制和优化 AI 应用<a href="#使用-cloudflare-ai-gateway-监控控制和优化-ai-应用"><span>#</span></a></h2><section><h3>为什么要用 Cloudflare AI Gateway？<a href="#为什么要用-cloudflare-ai-gateway"><span>#</span></a></h3><p>众所周知，AI 应用开发在调用各种 AI 服务时，往往会面临一些挑战，如：不能有效监控和控制成本、难以管理多个供应商等。现在大多数的解决方案都是通过oneapi或者newapi来解决这些问题，但是这些基本上都要自己再去部署一套服务，有时候我们就是单纯的想去监控一下key的使用和日志情况，这时候使用Cloudflare AI Gateway就是一个不错的选择。</p></section></section>
<section><h2>核心功能<a href="#核心功能"><span>#</span></a></h2><section><h3>1. 分析和监控<a href="#1-分析和监控"><span>#</span></a></h3><ul>
<li>实时查看请求数量、token 使用情况和成本估算</li>
<li>详细的日志记录，支持故障排查和审计</li>
<li>自定义过滤器，按时间和提供商类型筛选数据</li>
</ul></section><section><h3>2. 性能优化<a href="#2-性能优化"><span>#</span></a></h3><ul>
<li>智能缓存机制，提升响应速度</li>
<li>请求重试和模型回退策略</li>
<li>支持 WebSocket API，实现持续通信</li>
</ul></section><section><h3>3. 成本控制<a href="#3-成本控制"><span>#</span></a></h3><ul>
<li>统一的成本监控仪表板</li>
<li>速率限制功能，防止过度使用</li>
<li>自定义缓存策略，降低 API 调用成本</li>
</ul></section><section><h3>4. 多供应商支持<a href="#4-多供应商支持"><span>#</span></a></h3><p>AI Gateway 支持市面上主流的 AI 服务提供商，包括：</p><ul>
<li><a href="https://aws.amazon.com/bedrock/" target="_blank">Amazon Bedrock</a></li>
<li><a href="https://www.anthropic.com/" target="_blank">Anthropic</a></li>
<li><a href="https://azure.microsoft.com/products/cognitive-services/openai-service/" target="_blank">Azure OpenAI</a></li>
<li><a href="https://cohere.com/" target="_blank">Cohere</a></li>
<li><a href="https://deepseek.com/" target="_blank">DeepSeek AI</a></li>
<li><a href="https://makersuite.google.com/" target="_blank">Google AI Studio</a></li>
<li><a href="https://cloud.google.com/vertex-ai" target="_blank">Google Vertex AI</a></li>
<li><a href="https://grok.x.ai/" target="_blank">Grok</a></li>
<li><a href="https://groq.com/" target="_blank">Groq</a></li>
<li><a href="https://huggingface.co/" target="_blank">HuggingFace</a></li>
<li><a href="https://mistral.ai/" target="_blank">Mistral AI</a></li>
<li><a href="https://openai.com/" target="_blank">OpenAI</a></li>
<li><a href="https://openrouter.ai/" target="_blank">OpenRouter</a></li>
<li><a href="https://www.perplexity.ai/" target="_blank">Perplexity</a></li>
<li><a href="https://replicate.com/" target="_blank">Replicate</a></li>
<li><a href="https://workers.cloudflare.com/ai" target="_blank">Workers AI</a></li>
<li>Universal Endpoint</li>
</ul></section></section>
<section><h2>快速入门指南<a href="#快速入门指南"><span>#</span></a></h2><section><h3>1. 创建网关<a href="#1-创建网关"><span>#</span></a></h3><ol>
<li>登录 Cloudflare 控制台</li>
<li>导航至 AI &gt; AI Gateway</li>
<li>点击 “Create Gateway”</li>
<li>设置网关名称（限制64字符）
<figure><img src="https://i.111666.best/image/spMZxwdf5tHGdVXPwP0WAP.png" alt="image" /><figcaption>image</figcaption></figure></li>
</ol></section><section><h3>2. API 集成<a href="#2-api-集成"><span>#</span></a></h3><p>其实这一步就是将Cloudflare AI Gateway的API集成到你的项目中，这样就可以实现对你的项目进行监控和控制了。AI Gateway 提供两种集成方式：CURL 和 JS。
就是把你的api的请求地址换成Cloudflare AI Gateway的api地址，token还是一样的不变，
</p><figure><img src="https://i.111666.best/image/quDQR12QAYmVkS5GZBDvTT.png" alt="image" /><figcaption>image</figcaption></figure>
<figure><img src="https://i.111666.best/image/8kVmmbwGwN00ISWeX701mj.png" alt="image" /><figcaption>image</figcaption></figure><p></p><div><div><div></div><div>Important</div></div><div><p>需要注意的是，这里cloudflare会透传你的ip，所以你的ip会暴露在请求头中直接发送给AI服务商，因此被限制的地区的话还是不能调用api的，不能作为代理使用。</p></div></div></section></section>
<section><h2>结论<a href="#结论"><span>#</span></a></h2><p>Cloudflare AI Gateway 适合简单的监控和控制，对于一些大型的项目还是需要自己搭建一套服务来实现更加详细的追踪控制。</p></section>
<section><h2>参考资源<a href="#参考资源"><span>#</span></a></h2><ul>
<li><a href="https://developers.cloudflare.com/ai-gateway/" target="_blank">Cloudflare AI Gateway 官方文档</a></li>
<li><a href="https://developers.cloudflare.com/ai-gateway/get-started/" target="_blank">快速入门指南</a></li>
<li><a href="https://www.cloudflare.com/zh-cn/developer-platform/products/ai-gateway/" target="_blank">产品介绍页面</a></li>
</ul></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.wakaplus.com/posts/llama3-self-host-cost/</id>
      <title type="text">Llama-3 8B-Instruct自我托管的成本(转载)</title>
      <published>2024-06-15T00:00:00.000Z</published>
      <updated>2024-06-15T00:00:00.000Z</updated>
      <author><name>waka</name></author>
      <link rel="alternate" href="https://blog.wakaplus.com/posts/llama3-self-host-cost/"/>
      <summary type="text">对llama-3 8B-Instruct自我托管的成本进行分析</summary>
      <content type="html"><![CDATA[<section><h2>Cost Of Self Hosting Llama-3 8B-Instruct<a href="#cost-of-self-hosting-llama-3-8b-instruct"><span>#</span></a></h2><p>自我托管的成本 Llama-3 8B-Instruct</p><p>Sid Premkumar</p><p>, Sid Premkumar、Thu Jun 13 2024 2024 年 6 月 13 日星期四•<a href="https://blog.lytix.co/tags/llama-3" target="_blank">llama-3 骆驼-3</a></p><p></p><figure><img src="https://blog.lytix.co/posts/self-hosting-llama-3/cover.webp" alt="Cover Image" /><figcaption>Cover Image</figcaption></figure><p></p></section>
<section><h2>How much does it cost to self host a LLM?<a href="#how-much-does-it-cost-to-self-host-a-llm"><span>#</span></a></h2><p>自助托管 LLM 的费用是多少？<a href="https://blog.lytix.co/posts/self-hosting-llama-3#how-much-does-it-cost-to-self-host-a-llm" target="_blank"></a></p><section><h4>⚡️ TLDR: Assuming 100% utilization of your model <code>Llama-3 8B-Instruct</code> model costs about <span><span>17dollarsper1MtokenswhenselfhostingwithEKS,vsChatGPTwiththesameworkloadcanoffer17 dollars per 1M tokens when self hosting with EKS, vs ChatGPT with the same workload can offer </span><span><span><span></span><span>17</span><span>d</span><span>o</span><span>l</span><span>l</span><span>a</span><span>r</span><span>s</span><span>p</span><span>er</span><span>1</span><span>M</span><span>t</span><span>o</span><span>k</span><span>e</span><span>n</span><span>s</span><span>w</span><span>h</span><span>e</span><span>n</span><span>se</span><span>l</span><span>f</span><span>h</span><span>os</span><span>t</span><span>in</span><span>g</span><span>w</span><span>i</span><span>t</span><span>h</span><span>E</span><span>K</span><span>S</span><span>,</span><span></span><span>v</span><span>s</span><span>C</span><span>ha</span><span>tGP</span><span>T</span><span>w</span><span>i</span><span>t</span><span>h</span><span>t</span><span>h</span><span>es</span><span>am</span><span>e</span><span>w</span><span>or</span><span>k</span><span>l</span><span>o</span><span>a</span><span>d</span><span>c</span><span>an</span><span>o</span><span>f</span><span>f</span><span>er</span></span></span></span>1 per 1M tokens. Choosing to self host the hardware can make the cost &lt;$0.01 per 1M token that takes ~5.5 years to break even.<a href="#️-tldr-assuming-100-utilization-of-your-model-llama-3-8b-instruct-model-costs-about-17dollarsper1mtokenswhenselfhostingwitheksvschatgptwiththesameworkloadcanoffer17-dollars-per-1m-tokens-when-self-hosting-with-eks-vs-chatgpt-with-the-same-workload-can-offer-17dollarsper1mtokenswhenselfhostingwitheksvschatgptwiththesameworkloadcanoffer1-per-1m-tokens-choosing-to-self-host-the-hardware-can-make-the-cost-001-per-1m-token-that-takes-55-years-to-break-even"><span>#</span></a></h4><p>⚡️ TLDR：假设您的 <code>Llama-3 8B-Instruct</code> 模型 100% 使用 EKS 自托管，则每 100 万个代币的成本约为 17 美元，而 ChatGPT 在相同工作量下每 100 万个代币的成本仅为 1 美元。选择自行托管硬件可以使每 100 万个代币的成本低于 0.01 美元，这需要 ~5.5 年才能实现收支平衡。<a href="https://blog.lytix.co/posts/self-hosting-llama-3#%EF%B8%8F-tldr-assuming-100-utilization-of-your-model-llama-3-8b-instruct-model-costs-about-17-dollars-per-1m-tokens-when-self-hosting-with-eks-vs-chatgpt-with-the-same-workload-can-offer-1-per-1m-tokens-choosing-to-self-host-the-hardware-can-make-the-cost-001-per-1m-token-that-takes-55-years-to-break-even" target="_blank"></a></p><p>A question we often get is how do you scale with ChatGPT. One thing we wanted to try is to determine the cost of self hosting an open model such as Llama-3.<br />
我们经常遇到的一个问题是如何扩展 ChatGPT。我们想做的一件事就是确定自行托管 Llama-3 等开放模型的成本。</p><section><h5>Determining the best hardware<a href="#determining-the-best-hardware"><span>#</span></a></h5><p>确定最佳硬件<a href="https://blog.lytix.co/posts/self-hosting-llama-3#determining-the-best-hardware" target="_blank"></a></p><p><em>Context</em>: All tests were run on a EKS cluster. Each test was allocated the resources of the entire node, nothing else was running on that node other than system pods (prometheus, nvidia-daemon, etc.)<br />
背景：所有测试均在 EKS 集群上运行。每个测试都分配了整个节点的资源，除了系统 pod（prometheus、nvidia-daemon 等）外，该节点上没有运行其他任何东西。</p><p>We first wanted to start small, can we run it on a single Nvidia Tesla T4 GPU? I started with AWS’s <code>g4dn.2xlarge</code> instance. Its specs were as follows (<a href="https://aws.amazon.com/ec2/instance-types/g4/" target="_blank">source (opens in a new tab)</a>):<br />
我们首先想从小规模开始，能否在单个 Nvidia Tesla T4 GPU 上运行？我从 AWS 的 <code>g4dn.2xlarge</code> 实例开始。其规格如下（来源）：</p><ul>
<li>1 NVidia Tesla T4</li>
<li>32GB Memory. 32GB 内存</li>
<li>8 vCPUs 8 vCPU</li>
</ul><p>The short answer is that running either the 8B param or the 70B param version of Llama 3 did not work on this hardware. We initially tried the 70B param version of Llama 3 and constantly ran into OOM issues. We then sobered up and tried the 8B param version. Although this time we was able to get a response, generating the response took what felt like ~10 minutes. I checked <code>nvidia-smi</code> and the single GPU was being used, it just wasn’t enough.<br />
简而言之，在这种硬件上运行 8B 参数或 70B 参数版本的 Llama 3 都无法正常工作。我们最初尝试了 70B 参数版本的 Llama 3，但经常出现 OOM 问题。后来我们冷静下来，尝试了 8B 参数版本。虽然这次我们能得到响应，但生成响应却花了大约 10 分钟。我检查了 <code>nvidia-smi</code> ，发现单 GPU 也在使用，只是不够用。</p><p>For context 8B vs 70B refers to the parameters in the model and generally translate to performance of the model. The advantage of the 8B param is its small size and resource footprint.<br />
就上下文而言，8B 与 70B 指的是模型中的参数，一般转化为模型的性能。8B 参数的优点是体积小、占用资源少。</p><p></p><figure><img src="https://blog.lytix.co/posts/self-hosting-llama-3/cuda-oom.png" alt="CUDA OOM" /><figcaption>CUDA OOM</figcaption></figure><p></p><p>So I decided to switch to the <code>g4dn.16xlarge</code> instance type. The specs of that were as follows (<a href="https://aws.amazon.com/ec2/instance-types/g4/" target="_blank">source (opens in a new tab)</a>):<br />
因此，我决定改用 <code>g4dn.16xlarge</code> 实例类型。其规格如下（来源）：</p><ul>
<li>4 NVidia Tesla T4s</li>
<li>192gb memory 192GB 内存</li>
<li>48 vCPUs 48 个 vCPU</li>
</ul></section><section><h5>Initial Implementation 初步实施<a href="https://blog.lytix.co/posts/self-hosting-llama-3#initial-implementation" target="_blank"></a><a href="#initial-implementation初步实施"><span>#</span></a></h5><p>My initial implementation involved trying to copy and paste the code present on the llama-3 hugging face (<a href="https://huggingface.co/meta-llama/Meta-Llama-3-8B" target="_blank">see (opens in a new tab)</a>)<br />
我在最初的实施过程中，尝试复制和粘贴存在于 llama-3 抱抱脸上的代码（见）</p><p>These results seemed a lot more promising as the response time lowered from ~10m to sub 10 seconds consistently.<br />
这些结果似乎更有希望，因为响应时间从大约 10 米持续缩短到 10 秒以下。</p><p></p><figure><img src="https://blog.lytix.co/posts/self-hosting-llama-3/inital-implementation-results.png" alt="Initial Implementation Results" /><figcaption>Initial Implementation Results</figcaption></figure><p></p><p>I was able to get a response in a much more reasonable 5-7 seconds. At this point I wanted to start calculating the cost of this request.<br />
我只用了 5-7 秒就得到了合理得多的回复。此时，我想开始计算这个请求的成本。</p><p><code>g5dn.12xlarge</code> costs $3.912 per hour on demand (<a href="https://aws.amazon.com/ec2/instance-types/g5/" target="_blank">see (opens in a new tab)</a>).<br />
<code>g5dn.12xlarge</code> 的需求成本为每小时 3.912 美元（见）。</p><p>If we assume a full month of use, that costs:<br />
如果假定使用一个月，费用为</p><p>I had trouble with getting the token count in hugging face so ended up using <a href="https://belladoreai.github.io/llama-tokenizer-js/example-demo/build/" target="_blank">llama-tokenizer-js (opens in a new tab)</a> to get an approximation of tokens used.<br />
我在获取 “拥抱脸 “中的令牌数时遇到了麻烦，因此最终使用 llama-tokenizer-js 来获取令牌使用量的近似值。</p><p></p><figure><img src="https://blog.lytix.co/posts/self-hosting-llama-3/llama-3-tokenizer-js.png" alt="Llama-3 Tokenizer JS" /><figcaption>Llama-3 Tokenizer JS</figcaption></figure><p></p><p><strong>Note: This ended up being incorrect way of calculating the tokens used.<br />
注：这是计算所用代币的不正确方法。</strong></p><p>If we look at this result, we assume we used 39 tokens over ~6 seconds. Assuming Im processing tokens 24/7, extrapolating that we get:<br />
如果我们看一下这个结果，假设我们在 ~6 秒内使用了 39 个令牌。假定 Im 24/7 处理令牌，推断出：</p><p>With 6.5 tokens per second, over the month I can process:<br />
以每秒 6.5 个令牌计算，一个月下来我可以处理 6.5 个令牌：</p><p>With 16,848,000 tokens per month, every million tokens costs:<br />
每月有 1684.8 万个代币，每一百万个代币的成本：</p><p>😵Yikes. It is not looking good. Lets compare this to what ChatGPT 3.5 can get us.<br />
😵呀。看起来不妙。让我们把它与 ChatGPT 3.5 能带给我们的东西进行比较。</p><p>Looking at their <a href="https://openai.com/api/pricing/" target="_blank">pricing (opens in a new tab)</a>, ChatGPT 3.5 Turbo charges <span><span>0.5per1Minputtoken,and0.5 per 1M input token, and </span><span><span><span></span><span>0.5</span><span>p</span><span>er</span><span>1</span><span>M</span><span>in</span><span>p</span><span>u</span><span>tt</span><span>o</span><span>k</span><span>e</span><span>n</span><span>,</span><span></span><span>an</span><span>d</span></span></span></span>1.5 per 1M output token. For sake of simplicity, assuming an average input ratio, that means per 1M tokens they charge <span><span>1andthatsthenumbertobeat.Farfrommy1 and thats the number to beat. Far from my </span><span><span><span></span><span>1</span><span>an</span><span>d</span><span>t</span><span>ha</span><span>t</span><span>s</span><span>t</span><span>h</span><span>e</span><span>n</span><span>u</span><span>mb</span><span>er</span><span>t</span><span>o</span><span>b</span><span>e</span><span>a</span><span>t</span><span>.</span><span>F</span><span>a</span><span>r</span><span>f</span><span>r</span><span>o</span><span>mm</span><span>y</span></span></span></span>167.17.<br />
从定价来看，ChatGPT 3.5 Turbo 每 100 万个输入代币收取 0.5 美元，每 100 万个输出代币收取 1.5 美元。为简单起见，假设平均输入输出比为 1&lt;1&gt;，这意味着每 100 万个代币收费 1 美元，这就是要击败的数字。与我的 167.17 美元相差甚远。</p></section><section><h5>Realization Something Was Wrong<a href="#realization-something-was-wrong"><span>#</span></a></h5><p>意识到出了问题<a href="https://blog.lytix.co/posts/self-hosting-llama-3#realization-something-was-wrong" target="_blank"></a></p><p>At this point I felt I was doing something wrong. Llama 3 with 8B params is hard to run, but I didn’t feel it should be this hard, especially considering I have 4 GPUs available 🤔.<br />
此时，我觉得自己做错了什么。带有 8B 参数的《Llama 3》很难运行，但我觉得不应该这么难，尤其是考虑到我有 4 个 GPU 可用🤔。</p><p>I decided to try to use vLLM to host an API server instead of attempting to do it myself via hugging face libraries.<br />
我决定尝试使用 vLLM 来托管一个 API 服务器，而不是试图自己通过抱脸库来做这件事。</p><p>This was dead simple and just involved me installing <code>ray</code> and <code>vllm</code> via <code>pip3</code> and then changing my docker entry point to:<br />
这非常简单，我只需通过 <code>pip3</code> 安装 <code>ray</code> 和 <code>vllm</code> ，然后将 docker 入口点更改为</p><p>Specifically noting that I call <code>—tensor-parallel-size 4</code> to enforce that we use all 4 GPUs.<br />
特别要注意的是，我调用 <code>—tensor-parallel-size 4</code> 来强制使用全部 4 个 GPU。</p><p>Using this got <em>significantly</em> better results.<br />
这样做的效果明显更好。</p><p></p><figure><img src="https://blog.lytix.co/posts/self-hosting-llama-3/significantly-better-results.png" alt="Llama-3 Tokenizer JS" /><figcaption>Llama-3 Tokenizer JS</figcaption></figure><p></p><p>In this example you can see the query took 2044ms to complete. This was also the time I realized my previous method of calculating token usage was incorrect. vLLM returned the tokens used which was perfect.<br />
在这个示例中，您可以看到查询耗时 2044 毫秒才完成。这也是我意识到之前计算令牌使用量的方法不正确的时候。</p><p>Going back to cost, now we can calculate tokens per second:<br />
回到成本，现在我们可以计算每秒的代币：</p><p>Again assuming I produce tokens 24/7, this means that I can ingest a total of:<br />
再次假设我全天候生产代币，这意味着我总共可以摄入：</p><p>Which means the cost per 1 million tokens would cost me:<br />
也就是说，每 100 万个代币的成本就是我的成本：</p><p>Unfortunately this is still not below the value that ChatGPT offers, you’d lose about $17 a day 😭<br />
不幸的是，这仍然不低于 ChatGPT 提供的价值，您每天将损失约 17 美元 😭</p></section><section><h5>An Unconventional Approach<a href="#an-unconventional-approach"><span>#</span></a></h5><p>非常规方法<a href="https://blog.lytix.co/posts/self-hosting-llama-3#an-unconventional-approach" target="_blank"></a></p><p>Instead of using AWS another approach involves self hosting the hardware as well. Even after factoring in energy, this does dramatically lower the price.<br />
另一种方法是不使用 AWS，而是自行托管硬件。即使考虑到能源因素，这也能大幅降低价格。</p><p>Assuming we want to mirror our setup in AWS, we’d need 4xNVidia Tesla T4s. You can buy them for about $700 dollars on eBay<br />
假设我们想在 AWS 中镜像我们的设置，我们需要 4xNVidia Tesla T4。你可以在 eBay 上以约 700 美元的价格买到它们</p><p></p><figure><img src="https://blog.lytix.co/posts/self-hosting-llama-3/ebay-listing.png" alt="Ebay Listing" /><figcaption>Ebay Listing</figcaption></figure><p></p><p>Add in $1,000 to setup the rest of the rig and you have a final price of around:<br />
再加上安装其他设备的 1,000 美元，最终价格约为 1,000 美元：</p><p>If we calculate energy for this, we get about ~$50 bucks. I determined power consumption by 70W per GPU + 20W overhead:<br />
如果按此计算能耗，大约需要 50 美元。我按照每个 GPU 70W + 20W 的开销来计算功耗：</p><p></p><figure><img src="https://blog.lytix.co/posts/self-hosting-llama-3/energy-cost.png" alt="Energy Cost" /><figcaption>Energy Cost</figcaption></figure><p></p><p>After factoring in the ~<span><span>3,800fixedcost,youhaveamonthlycostof 3,800 fixed cost, you have a monthly cost of ~</span><span><span><span></span><span>3</span><span>,</span><span></span><span>800</span><span>f</span><span>i</span><span>x</span><span>e</span><span>d</span><span>cos</span><span>t</span><span>,</span><span></span><span>y</span><span>o</span><span>u</span><span>ha</span><span>v</span><span>e</span><span>am</span><span>o</span><span>n</span><span>t</span><span>h</span><span>l</span><span>y</span><span>cos</span><span>t</span><span>o</span><span>f</span><span> </span></span></span></span>50 bucks, lets round up to ~$100 to factor any misc items we might have missed.<br />
扣除约 3 800 美元的固定成本后，每月成本约为 50 美元，四舍五入后约为 100 美元，以考虑我们可能遗漏的任何杂项。</p><p>Re-calculating our cost per 1M tokens now:<br />
现在重新计算每 100 万个代币的成本：</p><p>Which is <strong>significantly</strong> cheaper than our ChatGPT costs.<br />
这比我们的 ChatGPT 费用便宜得多。</p><p>Trying to determine when you’d break even, assuming you want to produce 157,075,200 tokens with ChatGPT, you’re looking at a bill of:<br />
假定您想通过 ChatGPT 生产 157,075,200 个代币，要确定何时才能实现收支平衡，您需要支付的账单是</p><p>You have a fixed cost of ~<span><span>100amonth,whichresultsina‘profit’of100 a month, which results in a ‘profit’ of </span><span><span><span></span><span>100</span><span>am</span><span>o</span><span>n</span><span>t</span><span>h</span><span>,</span><span></span><span>w</span><span>hi</span><span>c</span><span>h</span><span>r</span><span>es</span><span>u</span><span>l</span><span>t</span><span>s</span><span>ina</span><span>‘</span><span>p</span><span>r</span><span>o</span><span>f</span><span>i</span><span>t</span><span>’</span><span>o</span><span>f</span></span></span></span>57. To make up your initial server cost of $3,800, you’d need about 66 months or 5.5 years to benefit from this approach.<br />
您每月的固定成本约为 100 美元，因此 “利润 “为 57 美元。若要弥补初始服务器成本 3,800 美元，您需要大约 66 个月或 5.5 年的时间才能从这种方法中获益。</p><p>Although this approach does come with negatives such as having to manage and scale your own hardware, it does seem to be possible to undercut the prices that ChatGPT offer by a significant amount in theory. In pracitice however, you’d have to evaluate how often you are utilizing your LLM, these hypotheticals all assume 100% utilization which is not realistic and would have to be tailord per use case.<br />
虽然这种方法有一些不利因素，比如需要管理和扩展自己的硬件，但从理论上讲，它确实有可能大大低于 ChatGPT 提供的价格。但在实际操作中，您必须评估 LLM 的使用频率，这些假设都是假定 100% 的使用率，但这并不现实，必须根据使用情况进行调整。</p><p><small><time>2024</time> © lytix.ai.</small></p></section></section></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.wakaplus.com/posts/wireguard/</id>
      <title type="text">Wireguard 搭建异地组网</title>
      <published>2024-06-13T00:00:00.000Z</published>
      <updated>2024-06-13T00:00:00.000Z</updated>
      <author><name>waka</name></author>
      <link rel="alternate" href="https://blog.wakaplus.com/posts/wireguard/"/>
      <summary type="text">Wireguard 搭建异地组网</summary>
      <content type="html"><![CDATA[<section><h3>前言<a href="#前言"><span>#</span></a></h3><p>之前就一直听说 Wireguard 可以进行异地组网，但是多次尝试发现只能达到点对点通信的效果，需要在每台机器上搭建客户端显然是不合理的。并且由于搭建了 tailscale，并且是基于 wireguard 的，所以就没再折腾了。</p><p>最近因为群友提到这个，所以又折腾了一下，并且互相交流了一番，发现果然可以实现异地组网。但是，组网却比 tailscale 麻烦上许多，并且有些出现的问题可能很难发现。但是基于本人多次尝试，发现了一个对自己来说比较方便的搭建方式。</p></section>
<section><h3>服务器搭建wg-easy<a href="#服务器搭建wg-easy"><span>#</span></a></h3><section><h4>1. 安装wireguard内核<a href="#1-安装wireguard内核"><span>#</span></a></h4><p>由于使用的是 centos7 服务器，默认内核不支持 wireguard。</p><p>如果不进行安装可能出现如下日志：</p><blockquote><p>Mon May 27 03:19&lt;09&gt; UTC 2024: Starting Wireguard /etc/wireguard/wg0.conf<br />
[#] ip link add wg0 type wireguard<br />
RTNETLINK answers: Not supported<br />
Unable to access interface: Protocol not supported<br />
[#] ip link delete dev wg0<br />
Cannot find device “wg0”<br />
Adding iptables NAT rule</p></blockquote><p>运行如下命令安装即可：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>yum</span><span> </span><span>update</span><span> &amp;&amp; </span><span>\</span><span> </span><span>yum</span><span> </span><span>install</span><span> </span><span>epel-release</span><span> </span><span>elrepo-release</span><span> </span><span>-y</span><span> &amp;&amp; </span><span>\</span><span> </span><span>yum</span><span> </span><span>install</span><span> </span><span>yum-plugin-elrepo</span><span> </span><span>-y</span><span> &amp;&amp; </span><span>\</span><span> </span><span>yum</span><span> </span><span>install</span><span> </span><span>kmod-wireguard</span><span> </span><span>wireguard-tools</span><span> </span><span>-y</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h4>2. 加载模块<a href="#2-加载模块"><span>#</span></a></h4><p>如果不进行如下命令操作，可能后续会出现能 ping 通，但是不能够进行 ssh 或者 http 访问的情况。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>sudo</span><span> </span><span>modprobe</span><span> </span><span>iptable_filter</span><span> &amp;&amp; </span><span>\</span><span> </span><span>sudo</span><span> </span><span>modprobe</span><span> </span><span>ip6table_filter</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h4>3. 安装wg-easy<a href="#3-安装wg-easy"><span>#</span></a></h4><p>wg-easy 是一个可以快速搭建 wireguard 服务端的项目，提供了一个方便的 ui 界面，生成客户端只需要在界面生成，然后即可下载配置文件。</p><p>当然了，之前因为没有搭建成功也是由于它的弊端，因为他的配置文件不能手动修改，<strong>所以就导致客户端配置了路由，但是服务器不知道往哪里发的问题</strong>。</p><p>所以，搭建 wg-easy 只是为了过度。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>version:</span><span> </span><span>'3.1'</span><span> </span><span>services:</span><span> </span><span>wg-easy:</span><span> </span><span>image:</span><span> </span><span>weejewel/wg-easy:nightly</span><span> </span><span>container_name:</span><span> </span><span>wg-easy</span><span> </span><span>environment:</span><span> </span><span>-</span><span> </span><span>WG_HOST=&lt;Server</span><span> </span><span>IP&gt;</span><span> </span><span>-</span><span> </span><span>PASSWORD=&lt;PASSWORD&gt;</span><span> </span><span>-</span><span> </span><span>WG_ALLOWED_IPS=10.8.0.0/24</span><span> </span><span>-</span><span> </span><span>WG_PERSISTENT_KEEPALIVE=</span><span>25</span><span> </span><span>-</span><span> </span><span>WG_DEFAULT_DNS=</span><span>114.114.114.114</span><span> </span><span>-</span><span> </span><span>UI_TRAFFIC_STATS=</span><span>true</span><span> </span><span>volumes:</span><span> </span><span>-</span><span> </span><span>/home/docker/wireguard:/etc/wireguard</span><span> </span><span>network_mode:</span><span> </span><span>"host"</span><span> </span><span># 51820 为 wireguard 数据传输的端口，51821 为 wg-easy 端口 # ports: # - 51820:51820/udp # - 51821:51821/tcp cap_add: - NET_ADMIN - SYS_MODULE privileged: true restart: unless-stopped</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这里需要注意的配置有三个：</p><ul>
<li>WG_HOST：填写服务器 IP</li>
<li>PASSWORD：访问 wg-easy 的密码</li>
<li>WG_ALLOWED_IPS：客户端路由地址，即客户端下载配置文件都会携带该配置</li>
</ul></section></section>
<section><h3>安装wireguard客户端<a href="#安装wireguard客户端"><span>#</span></a></h3><section><h4>创建客户端配置文件<a href="#创建客户端配置文件"><span>#</span></a></h4><p>访问 <code>ip:51821</code>，然后输入密码即可进入 wg-easy 图形界面，如下图所示。（如果需要配置反代可以自行配置）</p><p></p><figure><img src="https://img.amjun.com/uploads/2024/05/665437f709cbc.png" alt="20240527153617.png" /><figcaption>20240527153617.png</figcaption></figure><p></p><p>wireguard 官方客户端下载地址：<a href="https://www.nodeseek.com/jump?to=https%3A%2F%2Fwww.wireguard.com%2Finstall%2F%23ubuntu-module-tools" target="_blank">https://www.wireguard.com/install/#ubuntu-module-tools</a></p></section><section><h4>windows安装<a href="#windows安装"><span>#</span></a></h4><p>在网页端点击下载，然后导入配置文件即可。</p><p></p><figure><img src="https://img.amjun.com/uploads/2024/05/66543c67c40c0.png" alt="20240527155514.png" /><figcaption>20240527155514.png</figcaption></figure><p></p></section><section><h4>移动端安装<a href="#移动端安装"><span>#</span></a></h4><p>直接扫码即可导入配置。</p><p></p><figure><img src="https://img.amjun.com/uploads/2024/05/66543cc9892b2.png" alt="20240527155651.png" /><figcaption>20240527155651.png</figcaption></figure><p></p></section><section><h4>linux安装<a href="#linux安装"><span>#</span></a></h4><p>在 linux 上，个人喜欢使用 docker 进行安装。当然，如果使用的依然是 centos，那么还需要像上面那样<strong>安装 wireguard 内核</strong>。</p><p>下载配置文件，将其修改为 <code>wg0.conf</code>，然后放在 <code>/home/docker/wireguard</code> 目录下。</p><p>再运行如下命令：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>docker</span><span> </span><span>run</span><span> </span><span>-d</span><span> </span><span>\ </span><span>--privileged</span><span> </span><span>\ </span><span>--net=host</span><span> </span><span>\ </span><span>--restart=always</span><span> </span><span>\ </span><span>--name</span><span> </span><span>wireguard-client</span><span> </span><span>\ </span><span>--cap-add</span><span> </span><span>NET_ADMIN</span><span> </span><span>\ </span><span>--cap-add</span><span> </span><span>SYS_MODULE</span><span> </span><span>\ </span><span>-v</span><span> </span><span>/home/docker/wireguard/wg0.conf:/etc/wireguard/wg0.conf</span><span> </span><span>\ </span><span>--restart</span><span> </span><span>unless-stopped</span><span> </span><span>\ </span><span>cmulk/wireguard-docker:alpine</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section>
<section><h3>异地组网<a href="#异地组网"><span>#</span></a></h3><p>如果按上面的步骤，应该可以实现点对点进行通信，即连接上的客户端会像这样出现红点。</p><p></p><figure><img src="https://img.amjun.com/uploads/2024/05/665437f709cbc.png" alt="20240527153617.png" /><figcaption>20240527153617.png</figcaption></figure><p></p><p>比如网络情况如下，并且需要移动端能够实现移动端直接输入服务器 A 和服务器 B 的内网 ip 即可访问服务。</p><p></p><figure><img src="https://img.amjun.com/uploads/2024/05/6654438520010.png" alt="20240527162535.png" /><figcaption>20240527162535.png</figcaption></figure><p></p><p>要实现上述目的，要进行如下操作。</p><section><h4>1. 保证移动端路由<a href="#1-保证移动端路由"><span>#</span></a></h4><p></p><figure><img src="https://img.amjun.com/uploads/2024/05/66543cc9892b2.png" alt="20240527155651.png" /><figcaption>20240527155651.png</figcaption></figure><p></p><p>这里显示的配置意思是，wireguard 会代理 <code>10.8.0.0/24, 172.21.9.0/24, 172.30.1.0/24, 172.26.1.0/24, 172.20.2.0/23</code> 的流量，将所有匹配的流量都发往服务器。</p></section><section><h4>2. 修改服务器配置<a href="#2-修改服务器配置"><span>#</span></a></h4><p>例如我的内网设备为 <code>gw</code> 这台设备，当服务器收到 <code>172.21.9.0/24, 172.30.1.0/24, 172.26.1.0/24, 172.20.2.0/23</code> 请求时，需要将其转发到 <code>gw</code> 设备上。</p><p>修改对应的 <code>AllowedIPs</code> 即可。</p><p></p><figure><img src="https://img.amjun.com/uploads/2024/05/665444ff695d9.png" alt="20240527163153.png" /><figcaption>20240527163153.png</figcaption></figure><p></p><p>删除 <code>wg-easy</code> 容器。</p><blockquote><p>wg-easy 的作用在这里就结束了，它的作用就是为了方便地生成客户端地配置，现在客户端基本已经配置好了，并且需要修改 <code>wg0.conf</code>，而它会覆盖配置，所以需要删除。</p></blockquote><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>docker</span><span> </span><span>rm</span><span> </span><span>-f</span><span> </span><span>wg-easy</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>运行如下命令：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>docker</span><span> </span><span>run</span><span> </span><span>-d</span><span> </span><span>\ </span><span>--privileged</span><span> </span><span>\ </span><span>--net=host</span><span> </span><span>\ </span><span>--restart=always</span><span> </span><span>\ </span><span>--name</span><span> </span><span>wireguard-client</span><span> </span><span>\ </span><span>--cap-add</span><span> </span><span>NET_ADMIN</span><span> </span><span>\ </span><span>--cap-add</span><span> </span><span>SYS_MODULE</span><span> </span><span>\ </span><span>-v</span><span> </span><span>/home/docker/wireguard/wg0.conf:/etc/wireguard/wg0.conf</span><span> </span><span>\ </span><span>--restart</span><span> </span><span>unless-stopped</span><span> </span><span>\ </span><span>cmulk/wireguard-docker:alpine</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h4>3. 修改客户端配置<a href="#3-修改客户端配置"><span>#</span></a></h4><p><strong>如果内网设备为路由器，不需要进行该操作。</strong></p><p>首先内网设备需要开启 ipv4 转发，运行 <code>cat /proc/sys/net/ipv4/ip_forward</code> 如果结果为 1，表示转发已开启。</p><p>否则需要修改 <code>/etc/sysctl.conf</code> 文件，将 <code>#net.ipv4.ip_forward=1</code> 修改为 <code>net.ipv4.ip_forward=1</code>。再执行 <code>sysctl -p</code>。</p><p>另外，<code>wg0.conf</code> 需要添加如下配置：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>PostUp</span><span> </span><span>=</span><span> </span><span>iptables</span><span> </span><span>-A</span><span> </span><span>FORWARD</span><span> </span><span>-i</span><span> </span><span>client_route</span><span> </span><span>-j</span><span> </span><span>ACCEPT</span><span>; </span><span>iptables</span><span> </span><span>-A</span><span> </span><span>FORWARD</span><span> </span><span>-o</span><span> </span><span>client_route</span><span> </span><span>-j</span><span> </span><span>ACCEPT</span><span>; </span><span>iptables</span><span> </span><span>-t</span><span> </span><span>nat</span><span> </span><span>-A</span><span> </span><span>POSTROUTING</span><span> </span><span>-o</span><span> </span><span>eth0</span><span> </span><span>-j</span><span> </span><span>MASQUERADE</span><span>; </span><span>iptables</span><span> </span><span>-A</span><span> </span><span>FORWARD</span><span> </span><span>-i</span><span> </span><span>eth0</span><span> </span><span>-j</span><span> </span><span>ACCEPT</span><span>; </span><span>iptables</span><span> </span><span>-A</span><span> </span><span>FORWARD</span><span> </span><span>-o</span><span> </span><span>eth0</span><span> </span><span>-j</span><span> </span><span>ACCEPT</span><span>; </span><span>iptables</span><span> </span><span>-t</span><span> </span><span>nat</span><span> </span><span>-A</span><span> </span><span>POSTROUTING</span><span> </span><span>-o</span><span> </span><span>client_route</span><span> </span><span>-j</span><span> </span><span>MASQUERADE</span><span> </span><span>PostDown</span><span> </span><span>=</span><span> </span><span>iptables</span><span> </span><span>-D</span><span> </span><span>FORWARD</span><span> </span><span>-i</span><span> </span><span>client_route</span><span> </span><span>-j</span><span> </span><span>ACCEPT</span><span>; </span><span>iptables</span><span> </span><span>-D</span><span> </span><span>FORWARD</span><span> </span><span>-o</span><span> </span><span>client_route</span><span> </span><span>-j</span><span> </span><span>ACCEPT</span><span>; </span><span>iptables</span><span> </span><span>-t</span><span> </span><span>nat</span><span> </span><span>-D</span><span> </span><span>POSTROUTING</span><span> </span><span>-o</span><span> </span><span>eth0</span><span> </span><span>-j</span><span> </span><span>MASQUERADE</span><span>; </span><span>iptables</span><span> </span><span>-D</span><span> </span><span>FORWARD</span><span> </span><span>-i</span><span> </span><span>eth0</span><span> </span><span>-j</span><span> </span><span>ACCEPT</span><span>; </span><span>iptables</span><span> </span><span>-D</span><span> </span><span>FORWARD</span><span> </span><span>-o</span><span> </span><span>eth0</span><span> </span><span>-j</span><span> </span><span>ACCEPT</span><span>; </span><span>iptables</span><span> </span><span>-t</span><span> </span><span>nat</span><span> </span><span>-D</span><span> </span><span>POSTROUTING</span><span> </span><span>-o</span><span> </span><span>client_route</span><span> </span><span>-j</span><span> </span><span>MASQUERADE</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p></p><figure><img src="https://img.amjun.com/uploads/2024/05/6654461a5942c.png" alt="20240527163637.png" /><figcaption>20240527163637.png</figcaption></figure><p></p><p>如果不添加上面的配置，将服务路由到其他服务器，因为没有进行源地址 nat 转换，即使数据发送到了服务器 A/B 上，数据也回不来，因为它不知道 <code>10.8.0.0/24</code> 的路由。照上面的配置，在发送到其他服务器时，会将源地址转换成内网设备的地址。</p></section></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.wakaplus.com/posts/linux-kernel-plus/</id>
      <title type="text">Linux 内核参数调优</title>
      <published>2023-11-26T00:00:00.000Z</published>
      <updated>2023-11-26T00:00:00.000Z</updated>
      <author><name>waka</name></author>
      <link rel="alternate" href="https://blog.wakaplus.com/posts/linux-kernel-plus/"/>
      <summary type="text">Linux 内核参数调优简介</summary>
      <content type="html"><![CDATA[<section><h1>[技术分享] Linux 内核参数调优<a href="#技术分享-linux-内核参数调优"><span>#</span></a></h1><p>总结了一下, 贴出来一个 <code>/etc/sysctl.conf</code> 供参考. 实测效果良好.</p><p>推荐前提环境:</p><ul>
<li>Debian 10 / 11 / 12 (推荐有条件的一律升级至 Debian 12, 不用担心占用问题, 384M 内存以上也能运行良好, CentOS 就算了)</li>
<li><strong>KVM 虚拟化</strong>(LXC 没用过, 不保证可用)</li>
<li>384M 以上内存(更低的没有测试, 缓冲区相关配置均和内存相关需要实测)</li>
</ul><p>辅以安装 <a href="https://www.nodeseek.com/jump?to=https%3A%2F%2Fxanmod.org%2F" target="_blank">Xanmod 内核</a> 食用效果更佳.</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span># ------ 网络调优: 基本 ------</span></div></div><div><div><div>2</div></div><div><span># TTL 配置, Linux 默认 64</span></div></div><div><div><div>3</div></div><div><span># net.ipv4.ip_default_ttl=64</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span># 参阅 RFC 1323. 应当启用.</span></div></div><div><div><div>6</div></div><div><span>net.ipv4.tcp_timestamps=1</span></div></div><div><div><div>7</div></div><div><span># ------ END 网络调优: 基本 ------</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span># ------ 网络调优: 内核 Backlog 队列和缓存相关 ------</span></div></div><div><div><div>10</div></div><div><span># Ref: https://www.starduster.me/2020/03/02/linux-network-tuning-kernel-parameter/</span></div></div><div><div><div>11</div></div><div><span># Ref: https://blog.cloudflare.com/optimizing-tcp-for-high-throughput-and-low-latency/</span></div></div><div><div><div>12</div></div><div><span># Ref: https://zhuanlan.zhihu.com/p/149372947</span></div></div><div><div><div>13</div></div><div><span># 有条件建议依据实测结果调整相关数值</span></div></div><div><div><div>14</div></div><div><span># 缓冲区相关配置均和内存相关</span></div></div><div><div><div>15</div></div><div><span>net.core.wmem_default=16384</span></div></div><div><div><div>16</div></div><div><span>net.core.rmem_default=262144</span></div></div><div><div><div>17</div></div><div><span>net.core.rmem_max=536870912</span></div></div><div><div><div>18</div></div><div><span>net.core.wmem_max=536870912</span></div></div><div><div><div>19</div></div><div><span>net.ipv4.tcp_rmem=8192 262144 536870912</span></div></div><div><div><div>20</div></div><div><span>net.ipv4.tcp_wmem=4096 16384 536870912</span></div></div><div><div><div>21</div></div><div><span>net.ipv4.tcp_adv_win_scale=-2</span></div></div><div><div><div>22</div></div><div><span>net.ipv4.tcp_collapse_max_bytes=6291456</span></div></div><div><div><div>23</div></div><div><span>net.ipv4.tcp_notsent_lowat=131072</span></div></div><div><div><div>24</div></div><div><span>net.core.netdev_max_backlog=10240</span></div></div><div><div><div>25</div></div><div><span>net.ipv4.tcp_max_syn_backlog=10240</span></div></div><div><div><div>26</div></div><div><span>net.core.somaxconn=8192</span></div></div><div><div><div>27</div></div><div><span>net.ipv4.tcp_abort_on_overflow=1</span></div></div><div><div><div>28</div></div><div><span># 流控和拥塞控制相关调优</span></div></div><div><div><div>29</div></div><div><span># Egress traffic control 相关. 可选 fq, cake</span></div></div><div><div><div>30</div></div><div><span># 实测二者区别不大, 保持默认 fq 即可</span></div></div><div><div><div>31</div></div><div><span>net.core.default_qdisc=fq</span></div></div><div><div><div>32</div></div><div><span># Xanmod 内核 6.X 版本目前默认使用 bbr3, 无需设置</span></div></div><div><div><div>33</div></div><div><span># 实测比 bbr, bbr2 均有提升</span></div></div><div><div><div>34</div></div><div><span># 不过网络条件不同会影响. 有需求请实测.</span></div></div><div><div><div>35</div></div><div><span># net.ipv4.tcp_congestion_control=bbr3</span></div></div><div><div><div>36</div></div><div><span># 显式拥塞通知</span></div></div><div><div><div>37</div></div><div><span># 已被发现在高度拥塞的网络上是有害的.</span></div></div><div><div><div>38</div></div><div><span># net.ipv4.tcp_ecn=1</span></div></div><div><div><div>39</div></div><div><span># TCP 自动窗口</span></div></div><div><div><div>40</div></div><div><span># 要支持超过 64KB 的 TCP 窗口必须启用</span></div></div><div><div><div>41</div></div><div><span>net.ipv4.tcp_window_scaling=1</span></div></div><div><div><div>42</div></div><div><span># 开启后, TCP 拥塞窗口会在一个 RTO 时间</span></div></div><div><div><div>43</div></div><div><span># 空闲之后重置为初始拥塞窗口 (CWND) 大小.</span></div></div><div><div><div>44</div></div><div><span># 大部分情况下, 尤其是大流量长连接, 设置为 0.</span></div></div><div><div><div>45</div></div><div><span># 对于网络情况时刻在相对剧烈变化的场景, 设置为 1.</span></div></div><div><div><div>46</div></div><div><span>net.ipv4.tcp_slow_start_after_idle=0</span></div></div><div><div><div>47</div></div><div><span># nf_conntrack 调优</span></div></div><div><div><div>48</div></div><div><span># Add Ref: https://gist.github.com/lixingcong/0e13b4123d29a465e364e230b2e45f60</span></div></div><div><div><div>49</div></div><div><span>net.nf_conntrack_max=1000000</span></div></div><div><div><div>50</div></div><div><span>net.netfilter.nf_conntrack_max=1000000</span></div></div><div><div><div>51</div></div><div><span>net.netfilter.nf_conntrack_tcp_timeout_fin_wait=30</span></div></div><div><div><div>52</div></div><div><span>net.netfilter.nf_conntrack_tcp_timeout_time_wait=30</span></div></div><div><div><div>53</div></div><div><span>net.netfilter.nf_conntrack_tcp_timeout_close_wait=15</span></div></div><div><div><div>54</div></div><div><span>net.netfilter.nf_conntrack_tcp_timeout_established=300</span></div></div><div><div><div>55</div></div><div><span>net.ipv4.netfilter.ip_conntrack_tcp_timeout_established=7200</span></div></div><div><div><div>56</div></div><div><span># TIME-WAIT 状态调优</span></div></div><div><div><div>57</div></div><div><span># Ref: http://vincent.bernat.im/en/blog/2014-tcp-time-wait-state-linux.html</span></div></div><div><div><div>58</div></div><div><span># Ref: https://www.cnblogs.com/lulu/p/4149312.html</span></div></div><div><div><div>59</div></div><div><span># 4.12 内核中此参数已经永久废弃, 不用纠结是否需要开启</span></div></div><div><div><div>60</div></div><div><span># net.ipv4.tcp_tw_recycle=0</span></div></div><div><div><div>61</div></div><div><span>## 只对客户端生效, 服务器连接上游时也认为是客户端</span></div></div><div><div><div>62</div></div><div><span>net.ipv4.tcp_tw_reuse=1</span></div></div><div><div><div>63</div></div><div><span># 系统同时保持TIME_WAIT套接字的最大数量</span></div></div><div><div><div>64</div></div><div><span># 如果超过这个数字 TIME_WAIT 套接字将立刻被清除</span></div></div><div><div><div>65</div></div><div><span>net.ipv4.tcp_max_tw_buckets=55000</span></div></div><div><div><div>66</div></div><div><span># ------ END 网络调优: 内核 Backlog 队列和缓存相关 ------</span></div></div><div><div><div>67</div></div><div>
</div></div><div><div><div>68</div></div><div><span># ------ 网络调优: 其他 ------</span></div></div><div><div><div>69</div></div><div><span># Ref: https://zhuanlan.zhihu.com/p/149372947</span></div></div><div><div><div>70</div></div><div><span># Ref: https://www.starduster.me/2020/03/02/linux-network-tuning-kernel-parameter/#netipv4tcp_max_syn_backlog_netipv4tcp_syncookies</span></div></div><div><div><div>71</div></div><div><span># 启用选择应答</span></div></div><div><div><div>72</div></div><div><span># 对于广域网通信应当启用</span></div></div><div><div><div>73</div></div><div><span>net.ipv4.tcp_sack=1</span></div></div><div><div><div>74</div></div><div><span># 启用转发应答</span></div></div><div><div><div>75</div></div><div><span># 对于广域网通信应当启用</span></div></div><div><div><div>76</div></div><div><span>net.ipv4.tcp_fack=1</span></div></div><div><div><div>77</div></div><div><span># TCP SYN 连接超时重传次数</span></div></div><div><div><div>78</div></div><div><span>net.ipv4.tcp_syn_retries=3</span></div></div><div><div><div>79</div></div><div><span>net.ipv4.tcp_synack_retries=3</span></div></div><div><div><div>80</div></div><div><span># TCP SYN 连接超时时间, 设置为 5 约为 30s</span></div></div><div><div><div>81</div></div><div><span>net.ipv4.tcp_retries2=5</span></div></div><div><div><div>82</div></div><div><span># 开启 SYN 洪水攻击保护</span></div></div><div><div><div>83</div></div><div><span># 注意: tcp_syncookies 启用时, 此时实际上没有逻辑上的队列长度,</span></div></div><div><div><div>84</div></div><div><span># Backlog 设置将被忽略. syncookie 是一个出于对现实的妥协,</span></div></div><div><div><div>85</div></div><div><span># 严重违反 TCP 协议的设计, 会造成 TCP option 不可用, 且实现上</span></div></div><div><div><div>86</div></div><div><span># 通过计算 hash 避免维护半开连接也是一种 tradeoff 而非万金油,</span></div></div><div><div><div>87</div></div><div><span># 勿听信所谓“安全优化教程”而无脑开启</span></div></div><div><div><div>88</div></div><div><span>net.ipv4.tcp_syncookies=0</span></div></div><div><div><div>89</div></div><div>
</div></div><div><div><div>90</div></div><div><span># Ref: https://linuxgeeks.github.io/2017/03/20/212135-Linux%E5%86%85%E6%A0%B8%E5%8F%82%E6%95%B0rp_filter/</span></div></div><div><div><div>91</div></div><div><span># 开启反向路径过滤</span></div></div><div><div><div>92</div></div><div><span># Aliyun 负载均衡实例后端的 ECS 需要设置为 0</span></div></div><div><div><div>93</div></div><div><span>net.ipv4.conf.default.rp_filter=2</span></div></div><div><div><div>94</div></div><div><span>net.ipv4.conf.all.rp_filter=2</span></div></div><div><div><div>95</div></div><div>
</div></div><div><div><div>96</div></div><div><span># 减少处于 FIN-WAIT-2 连接状态的时间使系统可以处理更多的连接</span></div></div><div><div><div>97</div></div><div><span># Ref: https://www.cnblogs.com/kaishirenshi/p/11544874.html</span></div></div><div><div><div>98</div></div><div><span>net.ipv4.tcp_fin_timeout=10</span></div></div><div><div><div>99</div></div><div>
</div></div><div><div><div>100</div></div><div><span># Ref: https://xwl-note.readthedocs.io/en/latest/linux/tuning.html</span></div></div><div><div><div>101</div></div><div><span># 默认情况下一个 TCP 连接关闭后, 把这个连接曾经有的参数保存到dst_entry中</span></div></div><div><div><div>102</div></div><div><span># 只要 dst_entry 没有失效,下次新建立相同连接的时候就可以使用保存的参数来初始化这个连接.通常情况下是关闭的</span></div></div><div><div><div>103</div></div><div><span>net.ipv4.tcp_no_metrics_save=1</span></div></div><div><div><div>104</div></div><div><span># unix socket 最大队列</span></div></div><div><div><div>105</div></div><div><span>net.unix.max_dgram_qlen=1024</span></div></div><div><div><div>106</div></div><div><span># 路由缓存刷新频率</span></div></div><div><div><div>107</div></div><div><span>net.ipv4.route.gc_timeout=100</span></div></div><div><div><div>108</div></div><div>
</div></div><div><div><div>109</div></div><div><span># Ref: https://gist.github.com/lixingcong/0e13b4123d29a465e364e230b2e45f60</span></div></div><div><div><div>110</div></div><div><span># 启用 MTU 探测，在链路上存在 ICMP 黑洞时候有用（大多数情况是这样）</span></div></div><div><div><div>111</div></div><div><span>net.ipv4.tcp_mtu_probing = 1</span></div></div><div><div><div>112</div></div><div>
</div></div><div><div><div>113</div></div><div><span># No Ref</span></div></div><div><div><div>114</div></div><div><span># 开启并记录欺骗, 源路由和重定向包</span></div></div><div><div><div>115</div></div><div><span>net.ipv4.conf.all.log_martians=1</span></div></div><div><div><div>116</div></div><div><span>net.ipv4.conf.default.log_martians=1</span></div></div><div><div><div>117</div></div><div><span># 处理无源路由的包</span></div></div><div><div><div>118</div></div><div><span>net.ipv4.conf.all.accept_source_route=0</span></div></div><div><div><div>119</div></div><div><span>net.ipv4.conf.default.accept_source_route=0</span></div></div><div><div><div>120</div></div><div><span># TCP KeepAlive 调优</span></div></div><div><div><div>121</div></div><div><span># 最大闲置时间</span></div></div><div><div><div>122</div></div><div><span>net.ipv4.tcp_keepalive_time=300</span></div></div><div><div><div>123</div></div><div><span># 最大失败次数, 超过此值后将通知应用层连接失效</span></div></div><div><div><div>124</div></div><div><span>net.ipv4.tcp_keepalive_probes=2</span></div></div><div><div><div>125</div></div><div><span># 发送探测包的时间间隔</span></div></div><div><div><div>126</div></div><div><span>net.ipv4.tcp_keepalive_intvl=2</span></div></div><div><div><div>127</div></div><div><span># 系统所能处理不属于任何进程的TCP sockets最大数量</span></div></div><div><div><div>128</div></div><div><span>net.ipv4.tcp_max_orphans=262144</span></div></div><div><div><div>129</div></div><div><span># arp_table的缓存限制优化</span></div></div><div><div><div>130</div></div><div><span>net.ipv4.neigh.default.gc_thresh1=128</span></div></div><div><div><div>131</div></div><div><span>net.ipv4.neigh.default.gc_thresh2=512</span></div></div><div><div><div>132</div></div><div><span>net.ipv4.neigh.default.gc_thresh3=4096</span></div></div><div><div><div>133</div></div><div><span>net.ipv4.neigh.default.gc_stale_time=120</span></div></div><div><div><div>134</div></div><div><span>net.ipv4.conf.default.arp_announce=2</span></div></div><div><div><div>135</div></div><div><span>net.ipv4.conf.lo.arp_announce=2</span></div></div><div><div><div>136</div></div><div><span>net.ipv4.conf.all.arp_announce=2</span></div></div><div><div><div>137</div></div><div><span># ------ END 网络调优: 其他 ------</span></div></div><div><div><div>138</div></div><div>
</div></div><div><div><div>139</div></div><div><span># ------ 内核调优 ------</span></div></div><div><div><div>140</div></div><div>
</div></div><div><div><div>141</div></div><div><span># Ref: Aliyun, etc</span></div></div><div><div><div>142</div></div><div><span># 内核 Panic 后 1 秒自动重启</span></div></div><div><div><div>143</div></div><div><span>kernel.panic=1</span></div></div><div><div><div>144</div></div><div><span># 允许更多的PIDs, 减少滚动翻转问题</span></div></div><div><div><div>145</div></div><div><span>kernel.pid_max=32768</span></div></div><div><div><div>146</div></div><div><span># 内核所允许的最大共享内存段的大小（bytes）</span></div></div><div><div><div>147</div></div><div><span>kernel.shmmax=4294967296</span></div></div><div><div><div>148</div></div><div><span># 在任何给定时刻, 系统上可以使用的共享内存的总量（pages）</span></div></div><div><div><div>149</div></div><div><span>kernel.shmall=1073741824</span></div></div><div><div><div>150</div></div><div><span># 设定程序core时生成的文件名格式</span></div></div><div><div><div>151</div></div><div><span>kernel.core_pattern=core_%e</span></div></div><div><div><div>152</div></div><div><span># 当发生oom时, 自动转换为panic</span></div></div><div><div><div>153</div></div><div><span>vm.panic_on_oom=1</span></div></div><div><div><div>154</div></div><div><span># 表示强制Linux VM最低保留多少空闲内存（Kbytes）</span></div></div><div><div><div>155</div></div><div><span># vm.min_free_kbytes=1048576</span></div></div><div><div><div>156</div></div><div><span># 该值高于100, 则将导致内核倾向于回收directory和inode cache</span></div></div><div><div><div>157</div></div><div><span>vm.vfs_cache_pressure=250</span></div></div><div><div><div>158</div></div><div><span># 表示系统进行交换行为的程度, 数值（0-100）越高, 越可能发生磁盘交换</span></div></div><div><div><div>159</div></div><div><span>vm.swappiness=10</span></div></div><div><div><div>160</div></div><div><span># 仅用10%做为系统cache</span></div></div><div><div><div>161</div></div><div><span>vm.dirty_ratio=10</span></div></div><div><div><div>162</div></div><div><span>vm.overcommit_memory=1</span></div></div><div><div><div>163</div></div><div><span># 增加系统文件描述符限制</span></div></div><div><div><div>164</div></div><div><span># Fix error: too many open files</span></div></div><div><div><div>165</div></div><div><span>fs.file-max=1048575</span></div></div><div><div><div>166</div></div><div><span>fs.inotify.max_user_instances=8192</span></div></div><div><div><div>167</div></div><div><span>fs.inotify.max_user_instances=8192</span></div></div><div><div><div>168</div></div><div><span># 内核响应魔术键</span></div></div><div><div><div>169</div></div><div><span>kernel.sysrq=1</span></div></div><div><div><div>170</div></div><div><span># 弃用</span></div></div><div><div><div>171</div></div><div><span># net.ipv4.tcp_low_latency=1</span></div></div><div><div><div>172</div></div><div>
</div></div><div><div><div>173</div></div><div><span># Ref: https://gist.github.com/lixingcong/0e13b4123d29a465e364e230b2e45f60</span></div></div><div><div><div>174</div></div><div><span># 当某个节点可用内存不足时, 系统会倾向于从其他节点分配内存. 对 Mongo/Redis 类 cache 服务器友好</span></div></div><div><div><div>175</div></div><div><span>vm.zone_reclaim_mode=0</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.wakaplus.com/posts/linux-change-yum/</id>
      <title type="text">Centos7更换yum源</title>
      <published>2023-10-14T00:00:00.000Z</published>
      <updated>2023-10-14T00:00:00.000Z</updated>
      <author><name>waka</name></author>
      <link rel="alternate" href="https://blog.wakaplus.com/posts/linux-change-yum/"/>
      <summary type="text">Centos7更换yum源</summary>
      <content type="html"><![CDATA[<section><h1>Centos7更换yum源<a href="#centos7更换yum源"><span>#</span></a></h1><p>在 CentOS 7 中，默认的 Yum 软件包源可能在国内的网络环境下会有较慢的下载速度。本文旨在提供简明的步骤，指导你如何将默认的 Yum 源更换为阿里云的 Yum 源，从而获得更加流畅的软件包管理体验。</p><section><h2>背景<a href="#背景"><span>#</span></a></h2><p>Yum 源是 CentOS 等基于 Red Hat 的发行版用于管理与更新软件包的主要工具。由于网络等多种原因，国内访问国外 Yum 源的速度可能较慢，因此，更换为国内的镜像源，如阿里云提供的 Yum 源，通常能够得到更好的速度和稳定性。</p></section><section><h2>详细操作步骤<a href="#详细操作步骤"><span>#</span></a></h2><section><h3>步骤一：下载wget<a href="#步骤一下载wget"><span>#</span></a></h3><p>如果安装的Centos为最小版本，则系统中未安装<a href="https://so.csdn.net/so/search?q=wget%E5%91%BD%E4%BB%A4&amp;spm=1001.2101.3001.7020" target="_blank">wget命令</a>，因此需在更换yum源之前先将wget下载。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>yum</span><span> </span><span>-y</span><span> </span><span>install</span><span> </span><span>wget</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>步骤二：备份现有 Yum 源<a href="#步骤二备份现有-yum-源"><span>#</span></a></h3><p>在开始操作之前，建议备份现有的 Yum 源配置，以防不测。</p><section><h4>2.1 进入yum源目录下<a href="#21-进入yum源目录下"><span>#</span></a></h4><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>[root@bd01 ~]# cd /etc/yum.repos.d/</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h4>2.2 查看当前目录下的内容<a href="#22-查看当前目录下的内容"><span>#</span></a></h4><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>[root@bd01 yum.repos.d]# ls -l</span></div></div><div><div><div>2</div></div><div><span>total 40</span></div></div><div><div><div>3</div></div><div><span>-rw-r--r--. 1 root root 1572 Dec  1  2016 CentOS-Base.repo</span></div></div><div><div><div>4</div></div><div><span>-rw-r--r--. 1 root root 1309 Nov 23  2020 CentOS-CR.repo</span></div></div><div><div><div>5</div></div><div><span>-rw-r--r--. 1 root root  649 Nov 23  2020 CentOS-Debuginfo.repo</span></div></div><div><div><div>6</div></div><div><span>-rw-r--r--. 1 root root  314 Nov 23  2020 CentOS-fasttrack.repo</span></div></div><div><div><div>7</div></div><div><span>-rw-r--r--. 1 root root  630 Nov 23  2020 CentOS-Media.repo</span></div></div><div><div><div>8</div></div><div><span>-rw-r--r--. 1 root root 1331 Nov 23  2020 CentOS-Sources.repo</span></div></div><div><div><div>9</div></div><div><span>-rw-r--r--. 1 root root 8515 Nov 23  2020 CentOS-Vault.repo</span></div></div><div><div><div>10</div></div><div><span>-rw-r--r--. 1 root root  616 Nov 23  2020 CentOS-x86_64-kernel.repo</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h4>2.3 重命名yum源<a href="#23-重命名yum源"><span>#</span></a></h4><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>[root@bd01 yum.repos.d]# mv CentOS-Base.repo CentOS-Base.repo.bak</span></div></div><div><div><div>2</div></div><div><span>[root@bd01 yum.repos.d]# ls -l</span></div></div><div><div><div>3</div></div><div><span>total 40</span></div></div><div><div><div>4</div></div><div><span>-rw-r--r--. 1 root root 1572 Dec  1  2016 CentOS-Base.repo.bak</span></div></div><div><div><div>5</div></div><div><span>-rw-r--r--. 1 root root 1309 Nov 23  2020 CentOS-CR.repo</span></div></div><div><div><div>6</div></div><div><span>-rw-r--r--. 1 root root  649 Nov 23  2020 CentOS-Debuginfo.repo</span></div></div><div><div><div>7</div></div><div><span>-rw-r--r--. 1 root root  314 Nov 23  2020 CentOS-fasttrack.repo</span></div></div><div><div><div>8</div></div><div><span>-rw-r--r--. 1 root root  630 Nov 23  2020 CentOS-Media.repo</span></div></div><div><div><div>9</div></div><div><span>-rw-r--r--. 1 root root 1331 Nov 23  2020 CentOS-Sources.repo</span></div></div><div><div><div>10</div></div><div><span>-rw-r--r--. 1 root root 8515 Nov 23  2020 CentOS-Vault.repo</span></div></div><div><div><div>11</div></div><div><span>-rw-r--r--. 1 root root  616 Nov 23  2020 CentOS-x86_64-kernel.repo</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section><section><h3>步骤三：下载yum源<a href="#步骤三下载yum源"><span>#</span></a></h3><p>阿里-centos7：<a href="http://mirrors.aliyun.com/repo/Centos-7.repo" target="_blank">http://mirrors.aliyun.com/repo/Centos-7.repo</a>
网易-centos7：<a href="http://mirrors.163.com/.help/CentOS7-Base-163.repo" target="_blank">http://mirrors.163.com/.help/CentOS7-Base-163.repo</a>
这里提供两种yum源的下载地址，本下载需要虚拟机联网。
案例采用阿里的yum源，网易的操作步骤类同。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>cd</span><span> </span><span>/etc/yum.repos.d/</span></div></div><div><div><div>2</div></div><div><span>rm</span><span> </span><span>-f</span><span> </span><span>CentOS-Base.repo</span><span>  </span><span># 删除现有的 Yum 源配置文件</span></div></div><div><div><div>3</div></div><div><span>wget</span><span> </span><span>http://mirrors.aliyun.com/repo/Centos-7.repo</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>步骤四：修改yum源名称<a href="#步骤四修改yum源名称"><span>#</span></a></h3><p>将下载的yum源的名修改为系统默认的yum源名称。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>mv Centos-7.repo CentOS-Base.repo</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>步骤五：清理 Yum 缓存<a href="#步骤五清理-yum-缓存"><span>#</span></a></h3><p>更换 Yum 源后，我们需要清理 Yum 的缓存以确保下次安装或更新软件包时使用的是新源。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>yum</span><span> </span><span>clean</span><span> </span><span>all</span></div></div><div><div><div>2</div></div><div><span>yum</span><span> </span><span>makecache</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>步骤六：验证 Yum 源是否已更换成功<a href="#步骤六验证-yum-源是否已更换成功"><span>#</span></a></h3><p>我们可以尝试使用以下命令来更新系统，或者安装一个软件包以验证新的 Yum 源是否工作正常。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>yum</span><span> </span><span>-y</span><span> </span><span>update</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>或者尝试安装一个软件包：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>yum</span><span> </span><span>-y</span><span> </span><span>install</span><span> </span><span>vim</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>如果以上命令能够正常工作，说明 Yum 源已经成功更换为阿里云源。</p></section></section><section><h2>结语<a href="#结语"><span>#</span></a></h2><p>更换为阿里云的 Yum 源能够使我们在使用 CentOS 7 时获得更快的软件包下载速度，提高系统更新与维护的效率。希望本教程能够帮助到正在寻找如何更换 Yum 源的朋友们！</p><p>如遇到任何问题或疑问，欢迎留言交流。</p></section></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.wakaplus.com/posts/linux-kernel-update/</id>
      <title type="text">在 CentOS 7 上更新到最新 LTS 内核</title>
      <published>2023-10-14T00:00:00.000Z</published>
      <updated>2023-10-14T00:00:00.000Z</updated>
      <author><name>waka</name></author>
      <link rel="alternate" href="https://blog.wakaplus.com/posts/linux-kernel-update/"/>
      <summary type="text">在 CentOS 7 上更新到最新 LTS 内核教程</summary>
      <content type="html"><![CDATA[<section><h1>在 CentOS 7 上更新到最新 LTS 内核<a href="#在-centos-7-上更新到最新-lts-内核"><span>#</span></a></h1><p>本篇博客将介绍如何完成在 CentOS 7 系统中更换 Yum 源为阿里云源，并利用这个源更新到最新的长期支持（LTS）内核的过程。</p><p>仅查看版本信息</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>uname</span><span> </span><span>-r</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p></p><figure><img src="https://esay.579878700.xyz//i/2023/10/15/vqhwu.png" alt="img" /><figcaption>img</figcaption></figure><p></p><p>查看版本信息及相关内容</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>uname</span><span> </span><span>-a</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p></p><figure><img src="https://esay.579878700.xyz//i/2023/10/15/vsgfv.png" alt="img" /><figcaption>img</figcaption></figure><p></p><p>通过绝对路径查看查看版本信息及相关内容</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>cat</span><span> </span><span>/proc/version</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p></p><figure><img src="https://esay.579878700.xyz//i/2023/10/15/vt9wm.png" alt="img" /><figcaption>img</figcaption></figure><p></p><p>通过绝对路径查看查看版本信息</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>cat</span><span> </span><span>/etc/redhat-release</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p></p><figure><img src="https://esay.579878700.xyz//i/2023/10/15/vu42x.png" alt="img" /><figcaption>img</figcaption></figure><p></p><section><h2>1.安装最新的 LTS 内核<a href="#1安装最新的-lts-内核"><span>#</span></a></h2><p>更新版本使用yum,建议提前更新yum源仓库</p><p><strong>更新yum源仓库</strong></p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>yum -y update</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><img src="https://esay.579878700.xyz//i/2023/10/15/vvgjs.png" alt="" /></p><p>更新完成后，启用 ELRepo 仓库并安装ELRepo仓库的yum源</p><p> ELRepo 仓库是基于社区的用于企业级 Linux 仓库，提供对 RedHat Enterprise (RHEL) 和 其他基于 RHEL的 Linux 发行版（CentOS、Scientific、Fedora 等）的支持。</p><section><h4>1、导入ELRepo仓库的公共密钥<a href="#1导入elrepo仓库的公共密钥"><span>#</span></a></h4><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><img src="https://esay.579878700.xyz//i/2023/10/15/1w346e.png" alt="" /></p><p> 2、安装ELRepo仓库的yum源</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>rpm -Uvh http://www.elrepo.org/elrepo-release-7.0-3.el7.elrepo.noarch.rpm</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><img src="https://esay.579878700.xyz//i/2023/10/15/1w44s1.png" alt="" /></p></section><section><h4>3、查询可用内核版本<a href="#3查询可用内核版本"><span>#</span></a></h4><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>yum --disablerepo="*" --enablerepo="elrepo-kernel" list available</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><img src="https://esay.579878700.xyz//i/2023/10/15/1w5b15.png" alt="" /></p><p>  可选版本如下：yum —disablerepo=”*” —enablerepo=“elrepo-kernel” list available</p><p><img src="https://esay.579878700.xyz//i/2023/10/15/1xnsdo.png" alt="" /></p><p>其中lt表示long-term，即主线版本，该版本建议慎重选择。</p><p>其中mt表示latest mainline，即长期稳定版本，稳定可靠，建议安装该版本</p><p>如：yum -y —enablerepo=elrepo-kernel install kernel-lt</p></section><section><h4>4、安装最新的稳定版本内核<a href="#4安装最新的稳定版本内核"><span>#</span></a></h4><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>yum -y --enablerepo=elrepo-kernel install kernel-lt</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>会默认安装lt的最新版本，即5.4.207-1.el7.elrepo，也可根据具体需要指定对应版本。</p><p><img src="https://esay.579878700.xyz//i/2023/10/15/1wh87d.png" alt="" /></p></section><section><h3>如果国内下载的速度不行，可以试试阿里的源<a href="#如果国内下载的速度不行可以试试阿里的源"><span>#</span></a></h3><section><h4>5.1. 添加 ELRepo 的阿里云源<a href="#51-添加-elrepo-的阿里云源"><span>#</span></a></h4><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>nano</span><span> </span><span>/etc/yum.repos.d/elrepo.aliyun.repo</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>添加以下内容：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>[elrepo]</span></div></div><div><div><div>2</div></div><div><span>name=ELRepo.org Community Enterprise Linux Repository - el7</span></div></div><div><div><div>3</div></div><div><span>baseurl=https://mirrors.aliyun.com/elrepo/elrepo/el7/$basearch/</span></div></div><div><div><div>4</div></div><div><span><span>        </span></span><span>https://mirrors.tuna.tsinghua.edu.cn/elrepo/el7/$basearch/</span></div></div><div><div><div>5</div></div><div><span>enabled=1</span></div></div><div><div><div>6</div></div><div><span>gpgcheck=1</span></div></div><div><div><div>7</div></div><div><span>gpgkey=https://www.elrepo.org/RPM-GPG-KEY-elrepo.org</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>保存并退出。</p></section><section><h4>5.2. 安装最新 LTS 内核<a href="#52-安装最新-lts-内核"><span>#</span></a></h4><p>我们可以直接从阿里云的 ELRepo 源安装最新的 LTS 内核：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>yum</span><span> </span><span>--enablerepo=elrepo</span><span> </span><span>install</span><span> </span><span>kernel-lt</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section><section><h3>2. 配置 Grub<a href="#2-配置-grub"><span>#</span></a></h3><p>当下载完成的时候，我们需要告知系统使用新安装的内核版本。首先，编辑 Grub 配置文件：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>nano</span><span> </span><span>/etc/default/grub</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>设置 <code>GRUB_DEFAULT=0</code>，确保使用新内核启动系统。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>GRUB_DEFAULT=0</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>（注意，上面的那个是数字0，不是英文字母o）</p><p>接着，重新生成 Grub 配置：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>grub2-mkconfig</span><span> </span><span>-o</span><span> </span><span>/boot/grub2/grub.cfg</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section><section><h2>3.重启并验证<a href="#3重启并验证"><span>#</span></a></h2><p>最后，我们需要重启系统以使用新安装的 LTS 内核。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>reboot</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>重启完成后，使用以下命令验证是否成功使用新的内核版本：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>uname</span><span> </span><span>-r</span></div></div></code></pre><div><div></div><div></div></div></figure></div><section><h4>删除旧内核（<strong>可选</strong>）<a href="#删除旧内核可选"><span>#</span></a></h4><p>查看系统中的全部内核</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>rpm -qa | </span><span>grep</span><span> kernel</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>可选择删除3.10版本的内核</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>yum remove kernel</span><span>-</span><span>版本</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section><section><h2>结语<a href="#结语"><span>#</span></a></h2><p>至此，我们已经成功在 CentOS 7 上更新到最新的 LTS 内核。这个过程旨在提供一个更快速、更平滑的更新和安装过程，希望能帮到你。</p></section></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.wakaplus.com/posts/1panelapp/</id>
      <title type="text">1pane Linux管理面板安装第三方软件库和设置自动更新</title>
      <published>2023-09-17T00:00:00.000Z</published>
      <updated>2023-09-17T00:00:00.000Z</updated>
      <author><name>waka</name></author>
      <link rel="alternate" href="https://blog.wakaplus.com/posts/1panelapp/"/>
      <summary type="text">1pane Linux管理面板安装第三方软件库和设置自动更新</summary>
      <content type="html"><![CDATA[<section><h2>1panel Liunx管理面板安装第三方软件库并设置自动更新教程<a href="#1panel-liunx管理面板安装第三方软件库并设置自动更新教程"><span>#</span></a></h2><section><h3>1、准备：安装好的1panel面板<a href="#1准备安装好的1panel面板"><span>#</span></a></h3><p>1panel文档：<a href="https://1panel.cn/docs/" target="_blank">https://1panel.cn/docs/</a></p><p>默认 <code>1Panel</code> 安装在 <code>/opt/</code> 路径下，如果不是可按需修改。</p><p>关于大陆电磁环境复杂，<code>github</code> 网络连接可能有问题，可以自行搜寻解决方式，如 <code>ghproxy</code> 等。</p><p>项目地址：<a href="https://github.com/okxlin/appstore" target="_blank">https://github.com/okxlin/appstore</a></p></section><section><h3>2、使用方法<a href="#2使用方法"><span>#</span></a></h3><p>默认<code>1Panel</code>安装在<code>/opt/</code>路径下，如果不是按需修改以下。</p></section><section><h3>2.1 国内网络<a href="#21-国内网络"><span>#</span></a></h3><blockquote><p>GitHub加速方式</p><blockquote><ul>
<li>(本仓库已添加)自建：<a href="https://github.com/hunshcn/gh-proxy" target="_blank">https://github.com/hunshcn/gh-proxy</a></li>
<li><a href="https://ghp.ci/" target="_blank">https://ghp.ci</a></li>
</ul></blockquote></blockquote><section><h4>2.1.1 使用 git 命令获取应用<a href="#211-使用-git-命令获取应用"><span>#</span></a></h4><p><code>1Panel</code>计划任务类型<code>Shell 脚本</code>的计划任务框里，添加并执行以下命令，或者终端运行以下命令，</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>clone</span><span> </span><span>-b</span><span> </span><span>localApps</span><span> </span><span>https://ghp.ci/https://github.com/okxlin/appstore</span><span> </span><span>/opt/1panel/resource/apps/local/appstore-localApps</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>cp</span><span> </span><span>-rf</span><span> </span><span>/opt/1panel/resource/apps/local/appstore-localApps/apps/</span><span>*</span><span> </span><span>/opt/1panel/resource/apps/local/</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>rm</span><span> </span><span>-rf</span><span> </span><span>/opt/1panel/resource/apps/local/appstore-localApps</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>然后应用商店刷新本地应用即可。</p></section><section><h4>2.1.2 使用压缩包方式获取应用<a href="#212-使用压缩包方式获取应用"><span>#</span></a></h4><p><code>1Panel</code>计划任务类型<code>Shell 脚本</code>的计划任务框里，添加并执行以下命令，或者终端运行以下命令，</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>wget</span><span> </span><span>-P</span><span> </span><span>/opt/1panel/resource/apps/local</span><span> </span><span>https://ghp.ci/https://github.com/okxlin/appstore/archive/refs/heads/localApps.zip</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>unzip</span><span> </span><span>-o</span><span> </span><span>-d</span><span> </span><span>/opt/1panel/resource/apps/local/</span><span> </span><span>/opt/1panel/resource/apps/local/localApps.zip</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>cp</span><span> </span><span>-rf</span><span> </span><span>/opt/1panel/resource/apps/local/appstore-localApps/apps/</span><span>*</span><span> </span><span>/opt/1panel/resource/apps/local/</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>rm</span><span> </span><span>-rf</span><span> </span><span>/opt/1panel/resource/apps/local/appstore-localApps</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>rm</span><span> </span><span>-rf</span><span> </span><span>/opt/1panel/resource/apps/local/localApps.zip</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>然后应用商店刷新本地应用即可。</p></section></section><section><h3>2.2 国际互联网络<a href="#22-国际互联网络"><span>#</span></a></h3><section><h4>2.2.1 使用 git 命令获取应用<a href="#221-使用-git-命令获取应用"><span>#</span></a></h4><p><code>1Panel</code>计划任务类型<code>Shell 脚本</code>的计划任务框里，添加并执行以下命令，或者终端运行以下命令，</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>clone</span><span> </span><span>-b</span><span> </span><span>localApps</span><span> </span><span>https://github.com/okxlin/appstore</span><span> </span><span>/opt/1panel/resource/apps/local/appstore-localApps</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>cp</span><span> </span><span>-rf</span><span> </span><span>/opt/1panel/resource/apps/local/appstore-localApps/apps/</span><span>*</span><span> </span><span>/opt/1panel/resource/apps/local/</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>rm</span><span> </span><span>-rf</span><span> </span><span>/opt/1panel/resource/apps/local/appstore-localApps</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>然后应用商店刷新本地应用即可。</p></section><section><h4>2.2.2 使用压缩包方式获取应用<a href="#222-使用压缩包方式获取应用"><span>#</span></a></h4><p><code>1Panel</code>计划任务类型<code>Shell 脚本</code>的计划任务框里，添加并执行以下命令，或者终端运行以下命令，</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>wget</span><span> </span><span>-P</span><span> </span><span>/opt/1panel/resource/apps/local</span><span> </span><span>https://github.com/okxlin/appstore/archive/refs/heads/localApps.zip</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>unzip</span><span> </span><span>-o</span><span> </span><span>-d</span><span> </span><span>/opt/1panel/resource/apps/local/</span><span> </span><span>/opt/1panel/resource/apps/local/localApps.zip</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>cp</span><span> </span><span>-rf</span><span> </span><span>/opt/1panel/resource/apps/local/appstore-localApps/apps/</span><span>*</span><span> </span><span>/opt/1panel/resource/apps/local/</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>rm</span><span> </span><span>-rf</span><span> </span><span>/opt/1panel/resource/apps/local/appstore-localApps</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>rm</span><span> </span><span>-rf</span><span> </span><span>/opt/1panel/resource/apps/local/localApps.zip</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>然后应用商店刷新本地应用即可。</p></section></section></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.wakaplus.com/posts/fabric-bug/</id>
      <title type="text">在超级账本Hyperledger Fabric中开发智能合约遇到的问题</title>
      <published>2023-09-17T00:00:00.000Z</published>
      <updated>2023-09-17T00:00:00.000Z</updated>
      <author><name>waka</name></author>
      <link rel="alternate" href="https://blog.wakaplus.com/posts/fabric-bug/"/>
      <summary type="text">在超级账本Hyperledger Fabric中开发智能合约遇到的问题</summary>
      <content type="html"><![CDATA[<section><h2>1. 不要尝试获取节点本地的时间作为key或value的值<a href="#1-不要尝试获取节点本地的时间作为key或value的值"><span>#</span></a></h2><p>即使每个节点的本地的时间系统是经过校对一致的，但同一个交易在每个节点上执行的时间点不一定是相同的。如果使用time.now().unix()来取节点本地的时间作为key或value的值，会导致每个节点执行同一个交易的结果不一致，从而造成交易失败。正确的做法是使用函数GetTxTimestamp()来获取交易的时间。每个节点通过函数GetTxTimestamp()来获取到的交易时间是相同的<br />
例子：<br />
t, err := stub.GetTxTimestamp()<br />
t.Seconds // 交易的时间，unix时间戳，精确到秒<br />
t.Nanos// 交易的时间，unix时间戳，精确到纳秒<br />
time.Unix(t.Seconds, int64(t.Nanos)).UnixNano() / 1e6 // 交易的时间，unix时间戳，精确到毫秒</p><p>但需要注意的是，每个节点通过函数GetTxTimestamp()来获取到的交易时间是相同的，但它取到的时间是客户端创建交易时的本地时间。如果客户端的本地时间没有校准的话，GetTxTimestamp()获取到的交易时间也是不准确的。</p></section>
<section><h2>2. 在同一个区块里多笔交易对同一键进行更新<a href="#2-在同一个区块里多笔交易对同一键进行更新"><span>#</span></a></h2><p>对同一键进行更新的一个或多笔交易打包到同一块中，只有第1笔交易会成功，后面的交易会失败。相关的原因是：在一个区块里的多笔交易，只有在区块被提交后才会真正生效。而每一个键值都有版本号，假设一个键“key_1”在本个区块提交前的版本号为9，然后在本个区块里有2笔交易分别更新键”key_1”的值为”123”和”456”。那么当第1笔交易更新键“key_1”的值时，键”key_1”的版本号在本个区块里将更新为10，但在本区块未提交前，真正生效的版本号还是9。而当执行第2笔交易时，该交易读取到键”key_1”的版本号还是9，这样第2笔交易还是把键”key_1”的版本号更新为10，这样系统会判断为“双花”行为，从而引起冲突，导致只有第1笔交易会执行成功，第2笔交易执行失败。具体可以参考这篇文章 <a href="https://learnblockchain.cn/article/620" target="_blank">https://learnblockchain.cn/article/620</a><br />
简单地说，在同一个区块里多笔交易对同一键进行PutState()更新时，只有一笔交易是成功的，其它的交易会出现 MVCC_READ_CONFLICT报错。<br />
因此，试图在fabric合约里创建全局的计数器是一件不可靠的事情。</p></section>
<section><h2>3. 在同一个区块里多笔交易先后对同一个键进行更新和读取<a href="#3-在同一个区块里多笔交易先后对同一个键进行更新和读取"><span>#</span></a></h2><p>假设在一个区块里有2笔交易，第1笔交易对键”key_1”值更新为”123”，第2笔交易尝试读取键”key_1”的值，结果第2笔交易读键”key_1”的值结果为空。原因上面已经提及到，一个区块里的多笔交易，只有在区块被提交后才会真正生效。</p></section>
<section><h2>4. 在一个方法内对同一个键先写后读<a href="#4-在一个方法内对同一个键先写后读"><span>#</span></a></h2><p>在一个方法内对同一个键先进行写键值（putState()），然后再进行读键值（getState()），会发现前面的putState操作没有生效，原因也是同前面一样的道理</p></section>
<section><h2>5. 尝试通过合约使用加密算法对信息进行加密<a href="#5-尝试通过合约使用加密算法对信息进行加密"><span>#</span></a></h2><p>有些加密算法对同样内容的信息进行加密，有可能每次加密的结果是不一样的。例如：RSA公钥对同一数据加密，每次的结果都不一样。这样会导致不同的节点执行加密的结果不一样，从而导致提交的内容不一致，造成交易失败</p></section>
<section><h2>6. 没有了解query和invoke的合约函数调用方式<a href="#6-没有了解query和invoke的合约函数调用方式"><span>#</span></a></h2><p>超级账本调用合约函数的方式有2种：query和invoke<br />
通过query方式调用合约函数，只能查询链上数据，不能向链上写数据，并且不产生交易<br />
通过invoke方式调用合约函数，可以向链上写数据，并产生交易</p><p>如果一个合约函数里调用了PutState()操作，但是通过query方式来调用该合约函数，虽然不会报错，但PutState()操作实际没有生效，世界状态不会被改变</p><p>如果一个合约函数里没有调用PutState()操作，但是通过invoke方式来调用该合约函数，虽然不会报错，但却会产生交易</p></section>
<section><h2>7. 使用CouchDB，在使用select语句时没有对select的字段建立索引<a href="#7-使用couchdb在使用select语句时没有对select的字段建立索引"><span>#</span></a></h2><p>超级账本可以使用Leveldb或CouchDB。如果要支持富查询，一般会选择使用CouchDB。CouchDB可以支持类似于SQL的select语句来实现富查询。但是CouchDB的效率并不高，在不建立索引的情况下，可能对2-3万条的数据使用select语句就会导致查询非常耗时，从而导致合约函数的执行超时而失败。而对于传统的关系型数据库，2-3万条的数据量，在不建立索引的情况下，对查询的影响不是很明显。</p><p>例如下面的select语句：<br />
”{“selector”:{“StudentAge”:{“$lte”: 10}, “sort”:[{“RegisterTime”: “asc”}]}}”</p><p>最好就需要对字段StudentAge和RegisterTime建立索引。</p><p>想知道CouchDB在执行一条查询语句究竟有没有使用索引，用了哪些索引，可以通过向CouchDB发送post请求的方法，通过返回的response来查看使用了哪些索引。具体可参考下面的链接<br />
<a href="https://docs.couchdb.org/en/latest/api/database/find.html#db-explain" target="_blank">https://docs.couchdb.org/en/latest/api/database/find.html#db-explain</a><br />
1.3.8. /db/_explain 这一节的内容</p><p>具体的操作可以通过postman来实现，<br />
post的地址如<a href="http://127.0.0.1:5984/member_goods/_explain" target="_blank">http://127.0.0.1&lt;5984&gt;/member_goods/_explain</a><br />
发送的body就是查询select语句的内容，<br />
注意header里需要配置一个KeyValue Content-Type/json</p></section>
<section><h2>8. 使用CouchDB，在使用select语句时尽量避免使用sort进行排序<a href="#8-使用couchdb在使用select语句时尽量避免使用sort进行排序"><span>#</span></a></h2><p>sort排序会严重消耗查询时间，容易造成查询超时，应尽量避免使用。使用sort语句，必须对sort语句中涉及的字段创建索引，否则会报错。</p></section>
<section><h2>9.搭建Fabric区块链浏览器的坑<a href="#9搭建fabric区块链浏览器的坑"><span>#</span></a></h2><p>搭建Fabric区块链浏览器的时，如果之前曾经在同一台机器上使用不同的用户证书搭建过Fabric区块链浏览器，那么在同一台机器上使用新的用户证书搭建Fabric区块链浏览器时，一定要运行下面的命令清除之前的容器服务里的volume残留值：<br />
docker-compose down -v</p><p>详细可以参考这篇文章<br />
<a href="https://blog.csdn.net/lyz19961221/article/details/118576529" target="_blank">https://blog.csdn.net/lyz19961221/article/details/118576529</a></p></section>
<section><h2>10.避免Fabric的智能合约函数陷入死循环<a href="#10避免fabric的智能合约函数陷入死循环"><span>#</span></a></h2><p>合约函数陷入死循环，不但会导致本次合约函数执行超时，而且后面会导致无法执行其它合约函数的问题，后果非常严重，详细可以参考这篇文章<br />
<a href="https://zhuanlan.zhihu.com/p/572782459" target="_blank">https://zhuanlan.zhihu.com/p/572782459</a></p></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.wakaplus.com/posts/orderbook/</id>
      <title type="text">区块链中OrderBook的实现思路</title>
      <published>2023-09-17T00:00:00.000Z</published>
      <updated>2023-09-17T00:00:00.000Z</updated>
      <author><name>waka</name></author>
      <link rel="alternate" href="https://blog.wakaplus.com/posts/orderbook/"/>
      <summary type="text">solidity对OrderBook的实现思路</summary>
      <content type="html"><![CDATA[<section><h2>什么是OrderBook<a href="#什么是orderbook"><span>#</span></a></h2><p><strong>OrderBook是一个包含了所有交易者信息的订单集合</strong>，他们想买或者想卖。<strong>想买的order叫做bid，想卖的order叫做ask</strong>，这些bid和ask的order一旦满足了各自的条件，就会尽可能快的完成配对，促成一笔交易。这里有两个种类的order需要介绍一下，它们也是最为基本的交易订单。<strong>第一种是市价 market order，买和卖都是依据当前在orderbook能寻找到的最优价格执行</strong>。<strong>第二种是限价 limit order，买和卖都是按照已经确定量和价格进行</strong>。OrderBook是传统金融中一个很重要的交易方式，通过OrderBook中显示的信息，可以判断当前市场的供需关系以及价格关系，基于以上的判断可以对市场中的下一步发生的变化进行一个判断。<strong>在去中心化金融(Defi)，刚开始发展时也借鉴到了OrderBook的模式</strong>，由于传统的OrderBook由中心化机构进行给出，导致了信息中心化不透明。所以在Defi中想实现一个OrderBook完全只有买方和卖方进行参与，而机构只收取相依的手续费即可。<br />
看起来这样的想法是一种很不错的想法，提供了一个更加公平和自由度更高的一个市场让人们去操作。但是在实际中进行时会发现问题：</p><ul>
<li><strong>对于OrderBook需求来说整个区块链网络中交易成交的速率和数量都无法满足</strong>，即使以太坊的速率相较于比特币网络有了很大的提高。</li>
<li>在交易中有gas的存在，<strong>如果没有一种很好的方法去定位到合适的订单的话，随着订单数量的变多，找到相应订单所需的gas会消耗很大</strong>。并且每笔交易都有一个gas上限，当gas达到相应上限的时候就会导致交易失败。</li>
</ul></section>
<section><h2>实现OrderBook思路<a href="#实现orderbook思路"><span>#</span></a></h2></section>
<section><h2>一般的思路<a href="#一般的思路"><span>#</span></a></h2><p>抛开以太坊特殊的条件，我们在实现OrderBook的时候，采用的最常用的方法就是蛮力法。思路：</p><ul>
<li>创建买单和买单两个数组</li>
<li>当有买单创建的时候，对买单数组进行遍历，找到对应价格对应数量的订单，完成一笔交易。如果没找到，将买单加入买单数组。同理创建卖单时也进行相应的操作。</li>
</ul><p>这个方法能确保找到所需要的的交易，但是<strong>整个过程的时间复杂度为O(n)</strong>,然后可以进行并发和互斥锁结合，可以在短时间内处理大量的买单和买单满足需求。但是随着订单量的增多，所需要的时间也会增多，这在链上和链下的环境中都是不能接受的。<br />
但是，如果不采用蛮力法的话，将<strong>买单和卖单分别用排序二叉树的方式进行数据保存，那样的话时间复杂度就会变为O(logn)</strong>，这样可以优化查找，只是在插入是有点麻烦。</p></section>
<section><h2>换种思路<a href="#换种思路"><span>#</span></a></h2><p>在智能合约中我们要尽量避免时使用循环的方式，这是很费gas的。而为了确保能够得到想要的价格。我们可以这样设计：</p><ul>
<li>创建两个买单和卖单的两个映射
<ul>
<li><code>mapping(uint =&gt; mapping(uint =&gt; address[])) Bid;</code></li>
<li><code>mapping(uint =&gt; mapping(uint =&gt; address[])) Ask;</code></li>
<li><strong>前一个uint是价格，第二个uint是数量，最后面的是卖方或买方的地址</strong>；</li>
</ul>
</li>
<li>每次有买单和卖单进来的时候，在mapping中进行搜索，完成交易。搜索不到的话加入对应的mapping中即可。</li>
</ul><p>看上去这样的两个映射可以解决买卖双方的订单的查找的时间复杂度问题（<strong>将时间复杂度降为O(1)</strong>）。但是这样却忽略了一个问题，这样做我们无法看到整个市场的变化和趋势。我们这样设计代码的话只是实现了OrderBook的一部分功能。如果想要实现OrderBook，还有很多要进行修改的地方。</p></section>
<section><h2>第三种思路<a href="#第三种思路"><span>#</span></a></h2><p>在链上尽量不要使用大循环，但是我们可以将大循环变为小的循环，这样来说是可以的。同时我们也要反映整个OrderBook的变化，我们就可以设计一个压缩前缀树：</p><ul>
<li><strong>树中的每个节点为数字0~9，这样从头结点到叶子节点前的节点就可以连成一串数字，这个数字表示金额</strong>，每个叶子节点储存的是订单数组，每个订单保存了在此金额下的数量和地址。</li>
<li>同样树也要分两颗树，买家树和卖家树。</li>
<li><strong>每次有新金额出现时，要对树进行修改</strong>，对树进行扩展，保证从头到尾连成的数字为金额。</li>
<li><strong>同时当金额对应的叶子节点中数组长度为0时，要对树进行删除</strong>，这样做的目的是保证在对树遍历时减少时间消耗。</li>
<li><strong>在新买单创建时，先在卖家树中找到对应金额的数组，在对数组进行遍历找到对应金额(此时就将大循环优化为一个小循环)</strong>，找到对应订单后就成交交易。没找到的话就将其加入买家树。(同理，卖单也是这样处理)</li>
</ul><p>这样的话就可以兼顾市场变化和挂单的需求。</p></section>
<section><h2>问题<a href="#问题"><span>#</span></a></h2><p>以上的思路都可以实现OrderBook，但是还有许多问题需要进行考虑：</p><ul>
<li>如果对应的数量找不到，但是存在两个或两个以上的订单之和满足要求的时候，要怎么进行处理？<strong>在时间复杂度较小的前提下如何完成这个拆分寻找的过程？</strong></li>
<li><strong>整个树的结构要如何设计？才能使得在增删改查较为方便？</strong></li>
</ul></section>
<section><h2>结尾<a href="#结尾"><span>#</span></a></h2><p>虽然现在主流的DEX没有使用OrderBook，而是使用LP这样的方式，但是LP也会存在着很多的问题需要解决。正是因为OrderBook的一些功能无法满足Defi的需求，所以才会有LP的出现。技术的适用性是需要结合需求的。或许在以后可能会产生适应区块链的OrderBook。</p></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.wakaplus.com/posts/solidity-curd/</id>
      <title type="text">solidity动态数组的增删改查</title>
      <published>2023-09-17T00:00:00.000Z</published>
      <updated>2023-09-17T00:00:00.000Z</updated>
      <author><name>waka</name></author>
      <link rel="alternate" href="https://blog.wakaplus.com/posts/solidity-curd/"/>
      <summary type="text">solidity 内存(memory) 可变数组(动态数组) 的 增删改查</summary>
      <content type="html"><![CDATA[<section><h2>数组类型<a href="#数组类型"><span>#</span></a></h2><p>Solidity支持两种数组: 静态数组和动态数组。</p><p>又分<code>storage</code>与<code>memory</code>型数组</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>uint[] memory list;</span></div></div><div><div><div>2</div></div><div><span>//变长memory数组，使用前必须确定长度</span></div></div><div><div><div>3</div></div><div><span>list =new uint[](a);</span></div></div><div><div><div>4</div></div><div><span>//然后再使用，一般用在函数内</span></div></div><div><div><div>5</div></div><div><span>memory型数组不能使用pop,push也不能修改length</span></div></div></code></pre><div><div></div><div></div></div></figure></div><section><h3>静态数组<a href="#静态数组"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>// 固定长度为2的静态数组定义</span></div></div><div><div><div>2</div></div><div><span>uint[2] fixedArray;</span></div></div><div><div><div>3</div></div><div><span>//定长数组实例化</span></div></div><div><div><div>4</div></div><div><span>fixedArray = [4, 6];</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>静态数组不可新增元素，但可修改现有元素的值。</p></section><section><h3>动态数组<a href="#动态数组"><span>#</span></a></h3><p>动态数组，长度不固定，可以动态添加元素。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>//声明</span></div></div><div><div><div>2</div></div><div><span>uint[] dynamicArray;</span></div></div><div><div><div>3</div></div><div><span>//初始化，这里实例化一个长度为2的数组，值为0。</span></div></div><div><div><div>4</div></div><div><span>dynamicArray = new uint[](2);</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>// SPDX-License-Identifier: MIT</span></div></div><div><div><div>2</div></div><div><span>pragma</span><span> </span><span>solidity</span><span> </span><span>^0.8.9</span><span>;</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>library</span><span> </span><span>Array</span><span> {</span></div></div><div><div><div>5</div></div><div><span>    </span><span>function</span><span> </span><span>push</span><span>(</span><span>uint256</span><span>[] </span><span>memory</span><span> _nums, </span><span>uint256</span><span> _num) </span><span>internal</span><span> </span><span>pure</span><span> {</span></div></div><div><div><div>6</div></div><div><span>        </span><span>assembly</span><span> {</span></div></div><div><div><div>7</div></div><div><span>            </span><span>// 在可变数组的 末尾追加 一个value (_num)</span></div></div><div><div><div>8</div></div><div><span>            </span><span>mstore</span><span>(</span><span>add</span><span>(_nums, </span><span>mul</span><span>(</span><span>add</span><span>(</span><span>mload</span><span>(_nums), </span><span>1</span><span>), </span><span>0x20</span><span>)), _num)</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span>            </span><span>// 可变数组的length 加 1</span></div></div><div><div><div>11</div></div><div><span>            </span><span>mstore</span><span>(_nums, </span><span>add</span><span>(</span><span>mload</span><span>(_nums), </span><span>1</span><span>))</span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span>            </span><span>// 0x40 是空闲内存指针的预定义位置 (value 为 空闲指针开始位)</span></div></div><div><div><div>14</div></div><div><span>            </span><span>mstore</span><span>(</span><span>0x40</span><span>, </span><span>add</span><span>(</span><span>mload</span><span>(</span><span>0x40</span><span>), </span><span>0x20</span><span>))</span></div></div><div><div><div>15</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>16</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>17</div></div><div>
</div></div><div><div><div>18</div></div><div><span>    </span><span>function</span><span> </span><span>pop</span><span>(</span><span>uint256</span><span>[] </span><span>memory</span><span> _nums) </span><span>internal</span><span> </span><span>pure</span><span> </span><span>returns</span><span> (</span><span>uint256</span><span><span> </span><span>num_</span><span>) {</span></span></div></div><div><div><div>19</div></div><div><span>        </span><span>assembly</span><span> {</span></div></div><div><div><div>20</div></div><div><span>            </span><span>// 取出可变数组的最后一个value</span></div></div><div><div><div>21</div></div><div><span><span>            </span></span><span>num_ </span><span>:=</span><span> </span><span>mload</span><span>(</span><span>add</span><span>(_nums, </span><span>mul</span><span>(</span><span>mload</span><span>(_nums), </span><span>0x20</span><span>)))</span></div></div><div><div><div>22</div></div><div><span>            </span><span>// length - 1</span></div></div><div><div><div>23</div></div><div><span>            </span><span>mstore</span><span>(_nums, </span><span>sub</span><span>(</span><span>mload</span><span>(_nums), </span><span>1</span><span>))</span></div></div><div><div><div>24</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>25</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>26</div></div><div>
</div></div><div><div><div>27</div></div><div><span>    </span><span>function</span><span> </span><span>del</span><span>(</span><span>uint256</span><span>[] </span><span>memory</span><span> _nums, </span><span>uint256</span><span> _index) </span><span>internal</span><span> </span><span>pure</span><span> {</span></div></div><div><div><div>28</div></div><div><span>        </span><span>assembly</span><span> {</span></div></div><div><div><div>29</div></div><div><span>            </span><span>// 下标位置需 小于 数组.length</span></div></div><div><div><div>30</div></div><div><span>            </span><span>if</span><span> </span><span>lt</span><span>(_index, </span><span>mload</span><span>(_nums)) {</span></div></div><div><div><div>31</div></div><div><span>                </span><span>// 将最后一个value 移到 _index 下标处</span></div></div><div><div><div>32</div></div><div><span>                </span><span>mstore</span><span>(</span></div></div><div><div><div>33</div></div><div><span>                    </span><span>add</span><span>(_nums, </span><span>mul</span><span>(</span><span>add</span><span>(_index, </span><span>1</span><span>), </span><span>0x20</span><span>)),</span></div></div><div><div><div>34</div></div><div><span>                    </span><span>mload</span><span>(</span><span>add</span><span>(_nums, </span><span>mul</span><span>(</span><span>mload</span><span>(_nums), </span><span>0x20</span><span>)))</span></div></div><div><div><div>35</div></div><div><span><span>                </span></span><span>)</span></div></div><div><div><div>36</div></div><div><span>                </span><span>// length - 1</span></div></div><div><div><div>37</div></div><div><span>                </span><span>mstore</span><span>(_nums, </span><span>sub</span><span>(</span><span>mload</span><span>(_nums), </span><span>1</span><span>))</span></div></div><div><div><div>38</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>39</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>40</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>41</div></div><div>
</div></div><div><div><div>42</div></div><div><span>    </span><span>function</span><span> </span><span>update</span><span>(</span></div></div><div><div><div>43</div></div><div><span>        </span><span>uint256</span><span>[] </span><span>memory</span><span> _nums,</span></div></div><div><div><div>44</div></div><div><span>        </span><span>uint256</span><span> _index,</span></div></div><div><div><div>45</div></div><div><span>        </span><span>uint256</span><span> _num</span></div></div><div><div><div>46</div></div><div><span><span>    </span></span><span>) </span><span>internal</span><span> </span><span>pure</span><span> {</span></div></div><div><div><div>47</div></div><div><span><span>        </span></span><span>_nums[_index] </span><span>=</span><span> _num;</span></div></div><div><div><div>48</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>49</div></div><div>
</div></div><div><div><div>50</div></div><div><span>    </span><span>function</span><span> </span><span>get</span><span>(</span><span>uint256</span><span>[] </span><span>memory</span><span> _nums, </span><span>uint256</span><span> _index)</span></div></div><div><div><div>51</div></div><div><span>        </span><span>internal</span></div></div><div><div><div>52</div></div><div><span>        </span><span>pure</span></div></div><div><div><div>53</div></div><div><span>        </span><span>returns</span><span> (</span><span>uint256</span><span>)</span></div></div><div><div><div>54</div></div><div><span><span>    </span></span><span>{</span></div></div><div><div><div>55</div></div><div><span>        </span><span>return</span><span> _nums[_index];</span></div></div><div><div><div>56</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>57</div></div><div><span>}</span></div></div><div><div><div>58</div></div><div>
</div></div><div><div><div>59</div></div><div><span>contract</span><span> testArr</span><span> {</span></div></div><div><div><div>60</div></div><div><span>    </span><span>using</span><span> </span><span>Array</span><span> </span><span>for</span><span> </span><span>uint256</span><span>[];</span></div></div><div><div><div>61</div></div><div>
</div></div><div><div><div>62</div></div><div><span>    </span><span>function</span><span> </span><span>push</span><span>(</span><span>uint256</span><span>[] </span><span>memory</span><span> _nums, </span><span>uint256</span><span><span> </span><span>num</span><span>)</span></span></div></div><div><div><div>63</div></div><div><span>        </span><span>external</span></div></div><div><div><div>64</div></div><div><span>        </span><span>pure</span></div></div><div><div><div>65</div></div><div><span>        </span><span>returns</span><span> (</span><span>uint256</span><span>[] </span><span>memory</span><span>)</span></div></div><div><div><div>66</div></div><div><span><span>    </span></span><span>{</span></div></div><div><div><div>67</div></div><div><span><span>        </span></span><span>_nums.</span><span>push</span><span>(num);</span></div></div><div><div><div>68</div></div><div><span>        </span><span>return</span><span> _nums;</span></div></div><div><div><div>69</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>70</div></div><div>
</div></div><div><div><div>71</div></div><div><span>    </span><span>function</span><span> </span><span>pop</span><span>(</span><span>uint256</span><span>[] </span><span>memory</span><span> _nums)</span></div></div><div><div><div>72</div></div><div><span>        </span><span>external</span></div></div><div><div><div>73</div></div><div><span>        </span><span>pure</span></div></div><div><div><div>74</div></div><div><span>        </span><span>returns</span><span> (</span><span>uint256</span><span>[] </span><span>memory</span><span>, </span><span>uint256</span><span>)</span></div></div><div><div><div>75</div></div><div><span><span>    </span></span><span>{</span></div></div><div><div><div>76</div></div><div><span>        </span><span>uint256</span><span><span> num_ </span><span>=</span><span> _nums.</span></span><span>pop</span><span>();</span></div></div><div><div><div>77</div></div><div><span>        </span><span>return</span><span> (_nums, num_);</span></div></div><div><div><div>78</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>79</div></div><div>
</div></div><div><div><div>80</div></div><div><span>    </span><span>function</span><span> </span><span>del</span><span>(</span><span>uint256</span><span>[] </span><span>memory</span><span> _nums, </span><span>uint256</span><span> _index)</span></div></div><div><div><div>81</div></div><div><span>        </span><span>external</span></div></div><div><div><div>82</div></div><div><span>        </span><span>pure</span></div></div><div><div><div>83</div></div><div><span>        </span><span>returns</span><span> (</span><span>uint256</span><span>[] </span><span>memory</span><span>)</span></div></div><div><div><div>84</div></div><div><span><span>    </span></span><span>{</span></div></div><div><div><div>85</div></div><div><span><span>        </span></span><span>_nums.</span><span>del</span><span>(_index);</span></div></div><div><div><div>86</div></div><div><span>        </span><span>return</span><span> _nums;</span></div></div><div><div><div>87</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>88</div></div><div>
</div></div><div><div><div>89</div></div><div><span>    </span><span>function</span><span> </span><span>update</span><span>(</span></div></div><div><div><div>90</div></div><div><span>        </span><span>uint256</span><span>[] </span><span>memory</span><span> _nums,</span></div></div><div><div><div>91</div></div><div><span>        </span><span>uint256</span><span> _index,</span></div></div><div><div><div>92</div></div><div><span>        </span><span>uint256</span><span> _num</span></div></div><div><div><div>93</div></div><div><span><span>    </span></span><span>) </span><span>external</span><span> </span><span>pure</span><span> </span><span>returns</span><span> (</span><span>uint256</span><span>[] </span><span>memory</span><span>) {</span></div></div><div><div><div>94</div></div><div><span><span>        </span></span><span>_nums.</span><span>update</span><span>(_index, _num);</span></div></div><div><div><div>95</div></div><div><span>        </span><span>return</span><span> _nums;</span></div></div><div><div><div>96</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>97</div></div><div>
</div></div><div><div><div>98</div></div><div><span>    </span><span>function</span><span> </span><span>get</span><span>(</span><span>uint256</span><span>[] </span><span>memory</span><span> _nums, </span><span>uint256</span><span> _index)</span></div></div><div><div><div>99</div></div><div><span>        </span><span>external</span></div></div><div><div><div>100</div></div><div><span>        </span><span>pure</span></div></div><div><div><div>101</div></div><div><span>        </span><span>returns</span><span> (</span><span>uint256</span><span>)</span></div></div><div><div><div>102</div></div><div><span><span>    </span></span><span>{</span></div></div><div><div><div>103</div></div><div><span>        </span><span>return</span><span> _nums.</span><span>get</span><span>(_index);</span></div></div><div><div><div>104</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>105</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.wakaplus.com/posts/springboot-openai/</id>
      <title type="text">使用springboot整合OpenAI的Chatgpt api和Whisper api</title>
      <published>2023-09-17T00:00:00.000Z</published>
      <updated>2023-09-17T00:00:00.000Z</updated>
      <author><name>waka</name></author>
      <link rel="alternate" href="https://blog.wakaplus.com/posts/springboot-openai/"/>
      <summary type="text">本文详细介绍如何在Spring Boot项目中集成OpenAI的ChatGPT和Whisper API，包含环境搭建、依赖配置、接口调用等完整实现步骤，帮助开发者快速实现AI对话和语音转文字功能。</summary>
      <content type="html"><![CDATA[<section><h1>Springboot+Chatgpt+Whisper api<a href="#springbootchatgptwhisper-api"><span>#</span></a></h1><p><img src="https://www.qbitai.com/wp-content/uploads/2020/02/openai-cover-e1582087905568.png" alt="" /></p><p>本文通过springboot将两个OpenAI API的其中两个，ChatGPT和Whisper API集成在一起，方便后续的开发。</p><section><h2>OpenAI API<a href="#openai-api"><span>#</span></a></h2><p>OpenAI是人工智能和机器学习工具及API的领先提供商之一。它训练了尖端的语言模型，擅长理解和生成文本。OpenAI API提供对这些模型的访问，并可用于解决几乎涉及处理语言的任何任务。它可以执行包括但不限于以下任务：</p><ul>
<li>
<p>Content generation 内容生成</p>
</li>
<li>
<p>Summarization 总结</p>
</li>
<li>
<p>Classification, categorization, and sentiment analysis<br />
分类、归类和情感分析</p>
</li>
<li>
<p>Data extraction 数据提取</p>
</li>
<li>
<p>Translation 翻译</p>
</li>
<li>
<p>Transcription 转录</p>
</li>
<li>
<p>Image generation 图像生成</p>
</li>
</ul><p>我们可以通过任何语言发出HTTP请求与API进行交互。在本篇文章中，我们将使用Java构建一个Spring Boot微服务来集成Chatgpt API和Whisper API语音转文字功能。</p></section><section><h2>集成chatgpt-whisper-spring-boot<a href="#集成chatgpt-whisper-spring-boot"><span>#</span></a></h2><ol>
<li>
<p>首先我们打开IDEA新建一个springboot项目</p>
<p><img src="https://thumbsnap.com/i/3Y6zzgX5.png" alt="" /></p>
</li>
<li>
<p>然后往其中的pom.xml文件添加依赖</p>
<div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>&lt;</span><span>dependency</span><span>&gt;</span></span></div></div><div><div><div>2</div></div><div><span><span>            </span></span><span>&lt;</span><span>groupId</span><span>&gt;</span><span>org</span><span>.</span><span>springframework</span><span>.</span><span>boot</span><span><span>&lt;/</span><span>groupId</span><span>&gt;</span></span></div></div><div><div><div>3</div></div><div><span><span>            </span></span><span>&lt;</span><span>artifactId</span><span>&gt;</span><span>spring</span><span>-</span><span>boot</span><span>-</span><span>starter</span><span>-</span><span>web</span><span>&lt;/</span><span>artifactId</span><span>&gt;</span></div></div><div><div><div>4</div></div><div><span><span>        </span></span><span>&lt;/</span><span>dependency</span><span>&gt;</span></div></div><div><div><div>5</div></div><div><span><span>        </span></span><span>&lt;</span><span>dependency</span><span>&gt;</span></div></div><div><div><div>6</div></div><div><span><span>            </span></span><span>&lt;</span><span>groupId</span><span>&gt;</span><span>org</span><span>.</span><span>springframework</span><span>.</span><span>cloud</span><span><span>&lt;/</span><span>groupId</span><span>&gt;</span></span></div></div><div><div><div>7</div></div><div><span><span>            </span></span><span>&lt;</span><span>artifactId</span><span>&gt;</span><span>spring</span><span>-</span><span>cloud</span><span>-</span><span>starter</span><span>-</span><span>openfeign</span><span>&lt;/</span><span>artifactId</span><span>&gt;</span></div></div><div><div><div>8</div></div><div><span><span>            </span></span><span>&lt;</span><span>version</span><span>&gt;</span><span>4.0</span><span>.</span><span>1</span><span><span>&lt;/</span><span>version</span><span>&gt;</span></span></div></div><div><div><div>9</div></div><div><span><span>        </span></span><span>&lt;/</span><span>dependency</span><span>&gt;</span></div></div><div><div><div>10</div></div><div><span><span>        </span></span><span>&lt;</span><span>dependency</span><span>&gt;</span></div></div><div><div><div>11</div></div><div><span><span>            </span></span><span>&lt;</span><span>groupId</span><span>&gt;</span><span>org</span><span>.</span><span>projectlombok</span><span><span>&lt;/</span><span>groupId</span><span>&gt;</span></span></div></div><div><div><div>12</div></div><div><span><span>            </span></span><span>&lt;</span><span>artifactId</span><span>&gt;</span><span>lombok</span><span>&lt;/</span><span>artifactId</span><span>&gt;</span></div></div><div><div><div>13</div></div><div><span><span>            </span></span><span>&lt;</span><span>optional</span><span>&gt;</span><span>true</span><span><span>&lt;/</span><span>optional</span><span>&gt;</span></span></div></div><div><div><div>14</div></div><div><span><span>        </span></span><span>&lt;/</span><span>dependency</span><span>&gt;</span></div></div><div><div><div>15</div></div><div><span><span>        </span></span><span>&lt;</span><span>dependency</span><span>&gt;</span></div></div><div><div><div>16</div></div><div><span><span>            </span></span><span>&lt;</span><span>groupId</span><span>&gt;</span><span>org</span><span>.</span><span>springframework</span><span>.</span><span>boot</span><span><span>&lt;/</span><span>groupId</span><span>&gt;</span></span></div></div><div><div><div>17</div></div><div><span><span>            </span></span><span>&lt;</span><span>artifactId</span><span>&gt;</span><span>spring</span><span>-</span><span>boot</span><span>-</span><span>configuration</span><span>-</span><span>processor</span><span>&lt;/</span><span>artifactId</span><span>&gt;</span></div></div><div><div><div>18</div></div><div><span><span>            </span></span><span>&lt;</span><span>optional</span><span>&gt;</span><span>true</span><span><span>&lt;/</span><span>optional</span><span>&gt;</span></span></div></div><div><div><div>19</div></div><div><span><span>&lt;/</span><span>dependency</span><span>&gt;</span></span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div>
</li>
</ol></section><section><h2>Spring Cloud OpenFeign<a href="#spring-cloud-openfeign"><span>#</span></a></h2><p>对于第三方REST API集成，有许多可以用的工具，这里我们选择Spring Cloud OpenFeign，因为这个对于Spring Boot 3支持方面比较好。</p><p>接下来我们打开 <code>application.yml</code>，往其中添加下面的内容：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>openai</span><span>-</span><span>service</span></span><span>:</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>api</span><span>-</span><span>key</span><span>:</span><span> xxxxxxxxxx</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>gpt</span><span>-</span><span>model</span><span>:</span><span><span> gpt</span><span>-</span></span><span>3.5</span><span><span>-</span><span>turbo</span></span></div></div><div><div><div>4</div></div><div><span><span>  </span></span><span>audio</span><span>-</span><span>model</span><span>:</span><span><span> whisper</span><span>-</span></span><span>1</span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>http</span><span>-</span><span>client</span><span>:</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>read</span><span>-</span><span>timeout</span><span>:</span><span> </span><span>60000</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>connect</span><span>-</span><span>timeout</span><span>:</span><span> </span><span>60000</span></div></div><div><div><div>8</div></div><div><span><span>  </span></span><span>urls</span><span>:</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>base</span><span>-</span><span>url</span><span>:</span><span> https</span><span>:</span><span>//chat.579878700.xyz/v1</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>chat</span><span>-</span><span>url</span><span>:</span><span><span> </span><span>/</span><span>chat</span><span>/</span><span>completions</span></span></div></div><div><div><div>11</div></div><div><span><span>    </span></span><span>create</span><span>-</span><span>transcription</span><span>-</span><span>url</span><span>:</span><span><span> </span><span>/</span><span>audio</span><span>/</span><span>transcriptions</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>其中 <code> api-key</code>请填写自己的api key，如果您还没有API密钥，请到<a href="https://platform.openai.com/account/api-keys" target="_blank">https://platform.openai.com/account/api-keys</a> 以生成API密钥。实际开发中这种配置肯定要放在配置中心里面，但是我们今天先简单的写个demo。</p><p><code>gpt-model</code> ： <code>gpt-3.5-turbo</code> ，功能最强大的GPT-3.5模型，针对聊天进行了优化，成本仅为 <code>text-davinci-003</code> 的1/10，它可以执行任何语言任务，质量更好，输出更长，指令一致。</p><p><code>audio-model</code> ： <code>whisper-1</code> ，通用语音识别模型。它是在一个大型的多样化音频数据集上进行训练的，也是一个多任务模型，可以执行多语言语音识别以及语音翻译和来自世界各地大约100种不同语言的音频文件的语言识别。</p><p><code> read-time</code>和<code> connect-time</code>可以根据实际需求更改，这里我使用中文来回答，因此我设置的大一些，避免回答返回时间太长导致超时问题。</p><p><code> base-url</code>这里我填写的是我使用CF进行反代的地址，因为一些原因，openai api在中国不能直接访问，所以使用这个方法。如果不需要，请改成<code> https://api.openai.com/v1</code></p><p><code>urls</code> ：根据OpenAI API文档，所有API URL均以 <code>[https://api.openai.com/v1](https://api.openai.com/v1)</code> 开头。我们将其定义为 <code>base-url</code> 。对于聊天功能， <code>chat-url</code> 被定义为 <code>/chat/completions</code> 。对于音频转录， <code>create-transcription-url</code> 被定义为 <code>/audio/transcriptions</code> 。</p><p>我们使用 <code>/chat/completions</code> 继续使用 <code>gpt-3.5-turbo</code> 模型。</p></section><section><h2>Feign Client<a href="#feign-client"><span>#</span></a></h2><p>现在配置已经完成，让我们继续编写声明式Feign客户端，我们需要两个方法： <code>chat</code> 和 <code>createTranscription</code> ，调用它们各自的OpenAI API端点URL。你可以随意命名这些方法，为了简单起见，让我们继续使用 <code>chat</code> 和 <code>createTranscription</code> 。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>@</span><span>FeignClient</span><span>(</span></div></div><div><div><div>2</div></div><div><span>        </span><span>name</span><span><span> </span><span>=</span><span> </span></span><span>"openai-service"</span><span>,</span></div></div><div><div><div>3</div></div><div><span>        </span><span>url</span><span><span> </span><span>=</span><span> </span></span><span>"${openai-service.urls.base-url}"</span><span>,</span></div></div><div><div><div>4</div></div><div><span>        </span><span>configuration</span><span><span> </span><span>=</span><span> </span></span><span>OpenAIClientConfig</span><span>.</span><span>class</span></div></div><div><div><div>5</div></div><div><span>)</span></div></div><div><div><div>6</div></div><div><span>public</span><span> </span><span>interface</span><span><span> </span><span>OpenAIClient</span><span> </span></span><span>{</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>@</span><span>PostMapping</span><span>(</span><span>value</span><span><span> </span><span>=</span><span> </span></span><span>"${openai-service.urls.chat-url}"</span><span>, </span><span>headers</span><span><span> </span><span>=</span><span> {</span></span><span>"Content-Type=application/json"</span><span>})</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>ChatGPTResponse</span><span> </span><span>chat</span><span>(@</span><span>RequestBody</span><span> </span><span>ChatGPTRequest</span><span><span> </span><span>chatGPTRequest</span><span>);</span></span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>@</span><span>PostMapping</span><span>(</span><span>value</span><span><span> </span><span>=</span><span> </span></span><span>"${openai-service.urls.create-transcription-url}"</span><span>, </span><span>headers</span><span><span> </span><span>=</span><span> {</span></span><span>"Content-Type=multipart/form-data"</span><span>})</span></div></div><div><div><div>11</div></div><div><span><span>    </span></span><span>WhisperTranscriptionResponse</span><span> </span><span>createTranscription</span><span>(@</span><span>ModelAttribute</span><span> </span><span>WhisperTranscriptionRequest</span><span><span> </span><span>whisperTranscriptionRequest</span><span>);</span></span></div></div><div><div><div>12</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>Models<a href="#models"><span>#</span></a></h2><p>通过严格遵循API文档，我们先定义请求/响应类。请参见下面使用 <code>gpt-3.5-turbo</code> 模型的示例请求和响应类。注意，使用 <code>text-davinci-003</code> 完成的请求/响应略有不同，有关详细信息，请参阅API文档。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>@</span><span>Data</span></div></div><div><div><div>2</div></div><div><span>@</span><span>Builder</span></div></div><div><div><div>3</div></div><div><span>@</span><span>AllArgsConstructor</span></div></div><div><div><div>4</div></div><div><span>@</span><span>NoArgsConstructor</span></div></div><div><div><div>5</div></div><div><span>public</span><span> </span><span>class</span><span><span> </span><span>ChatGPTRequest</span><span> </span></span><span>implements</span><span><span> </span><span>Serializable</span><span> </span></span><span>{</span></div></div><div><div><div>6</div></div><div><span>    </span><span>private</span><span> </span><span>String</span><span> model</span><span>;</span></div></div><div><div><div>7</div></div><div><span>    </span><span>private</span><span> </span><span>List</span><span>&lt;</span><span>Message</span><span>&gt; messages</span><span>;</span></div></div><div><div><div>8</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>@</span><span>Data</span></div></div><div><div><div>2</div></div><div><span>public</span><span> </span><span>class</span><span><span> </span><span>ChatGPTResponse</span><span> </span></span><span>implements</span><span><span> </span><span>Serializable</span><span> </span></span><span>{</span></div></div><div><div><div>3</div></div><div><span>    </span><span>private</span><span> </span><span>String</span><span> id</span><span>;</span></div></div><div><div><div>4</div></div><div><span>    </span><span>private</span><span> </span><span>String</span><span> object</span><span>;</span></div></div><div><div><div>5</div></div><div><span>    </span><span>private</span><span> </span><span>String</span><span> model</span><span>;</span></div></div><div><div><div>6</div></div><div><span>    </span><span>private</span><span> </span><span>LocalDate</span><span> created</span><span>;</span></div></div><div><div><div>7</div></div><div><span>    </span><span>private</span><span> </span><span>List</span><span>&lt;</span><span>Choice</span><span>&gt; choices</span><span>;</span></div></div><div><div><div>8</div></div><div><span>    </span><span>private</span><span> </span><span>Usage</span><span> usage</span><span>;</span></div></div><div><div><div>9</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>使用 Whisper 模型的音频转录请求/响应示例：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>@</span><span>Data</span></div></div><div><div><div>2</div></div><div><span>@</span><span>Builder</span></div></div><div><div><div>3</div></div><div><span>@</span><span>AllArgsConstructor</span></div></div><div><div><div>4</div></div><div><span>@</span><span>NoArgsConstructor</span></div></div><div><div><div>5</div></div><div><span>public</span><span> </span><span>class</span><span><span> </span><span>TranscriptionRequest</span><span> </span></span><span>implements</span><span><span> </span><span>Serializable</span><span> </span></span><span>{</span></div></div><div><div><div>6</div></div><div><span>    </span><span>private</span><span> </span><span>MultipartFile</span><span> file</span><span>;</span></div></div><div><div><div>7</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>@</span><span>Data</span></div></div><div><div><div>2</div></div><div><span>public</span><span> </span><span>class</span><span><span> </span><span>WhisperTranscriptionResponse</span><span> </span></span><span>implements</span><span><span> </span><span>Serializable</span><span> </span></span><span>{</span></div></div><div><div><div>3</div></div><div><span>    </span><span>private</span><span> </span><span>String</span><span> text</span><span>;</span></div></div><div><div><div>4</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>Service<a href="#service"><span>#</span></a></h2><p>下面是服务层类 <code>OpenAIClientService</code> ，构造模型并调用Feignclient以触发对OpenAI API的调用。</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>@</span><span>Service</span></div></div><div><div><div>2</div></div><div><span>@</span><span>RequiredArgsConstructor</span></div></div><div><div><div>3</div></div><div><span>public</span><span> </span><span>class</span><span><span> </span><span>OpenAIClientService</span><span> </span></span><span>{</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>    </span><span>private</span><span> </span><span>final</span><span> </span><span>OpenAIClient</span><span> openAIClient</span><span>;</span></div></div><div><div><div>6</div></div><div><span>    </span><span>private</span><span> </span><span>final</span><span> </span><span>OpenAIClientConfig</span><span> openAIClientConfig</span><span>;</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>    </span><span>private</span><span> </span><span>final</span><span> </span><span>static</span><span> </span><span>String</span><span> ROLE_USER </span><span><span>=</span><span> </span></span><span>"user"</span><span>;</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span>    </span><span>public</span><span> </span><span>ChatGPTResponse</span><span> </span><span>chat</span><span>(</span><span>ChatRequest</span><span><span> </span><span>chatRequest</span><span>){</span></span></div></div><div><div><div>11</div></div><div><span><span>        </span></span><span>Message</span><span> message </span><span><span>=</span><span> </span></span><span>Message</span><span>.</span><span>builder</span><span>()</span></div></div><div><div><div>12</div></div><div><span><span>                </span></span><span>.</span><span>role</span><span>(ROLE_USER)</span></div></div><div><div><div>13</div></div><div><span><span>                </span></span><span>.</span><span>content</span><span>(</span><span>chatRequest</span><span>.</span><span>getQuestion</span><span>())</span></div></div><div><div><div>14</div></div><div><span><span>                </span></span><span>.</span><span>build</span><span>();</span></div></div><div><div><div>15</div></div><div><span><span>        </span></span><span>ChatGPTRequest</span><span> chatGPTRequest </span><span><span>=</span><span> </span></span><span>ChatGPTRequest</span><span>.</span><span>builder</span><span>()</span></div></div><div><div><div>16</div></div><div><span><span>                </span></span><span>.</span><span>model</span><span>(</span><span>openAIClientConfig</span><span>.</span><span>getModel</span><span>())</span></div></div><div><div><div>17</div></div><div><span><span>                </span></span><span>.</span><span>messages</span><span>(</span><span>Collections</span><span>.</span><span>singletonList</span><span>(message))</span></div></div><div><div><div>18</div></div><div><span><span>                </span></span><span>.</span><span>build</span><span>();</span></div></div><div><div><div>19</div></div><div><span>        </span><span>return</span><span> </span><span>openAIClient</span><span>.</span><span>chat</span><span>(chatGPTRequest);</span></div></div><div><div><div>20</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>21</div></div><div>
</div></div><div><div><div>22</div></div><div><span>    </span><span>public</span><span> </span><span>WhisperTranscriptionResponse</span><span> </span><span>createTranscription</span><span>(</span><span>TranscriptionRequest</span><span><span> </span><span>transcriptionRequest</span><span>){</span></span></div></div><div><div><div>23</div></div><div><span><span>        </span></span><span>WhisperTranscriptionRequest</span><span> whisperTranscriptionRequest </span><span><span>=</span><span> </span></span><span>WhisperTranscriptionRequest</span><span>.</span><span>builder</span><span>()</span></div></div><div><div><div>24</div></div><div><span><span>                </span></span><span>.</span><span>model</span><span>(</span><span>openAIClientConfig</span><span>.</span><span>getAudioModel</span><span>())</span></div></div><div><div><div>25</div></div><div><span><span>                </span></span><span>.</span><span>file</span><span>(</span><span>transcriptionRequest</span><span>.</span><span>getFile</span><span>())</span></div></div><div><div><div>26</div></div><div><span><span>                </span></span><span>.</span><span>build</span><span>();</span></div></div><div><div><div>27</div></div><div><span>        </span><span>return</span><span> </span><span>openAIClient</span><span>.</span><span>createTranscription</span><span>(whisperTranscriptionRequest);</span></div></div><div><div><div>28</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>29</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>请注意，除了必需参数之外，我们还可以在请求正文中传递多个可选参数来指示 OpenAI API。例如，对于<code>/chat/completions</code>，我们可以传入<code>temperature</code>参数，它告诉 OpenAI API 使用什么采样温度，介于 0 和 2 之间。较高的值将使输出更具随机性和风险性，而较低的值会使它更集中和确定性。<code>temperature</code>如果我们不指定，默认值为 1。请参阅<a href="https://platform.openai.com/docs/api-reference" target="_blank">OpenAI API 文档，</a>了解我们可以在请求正文中传递的所有可能的参数。</p></section><section><h2>REST Controller REST<a href="#rest-controller-rest"><span>#</span></a></h2><p>最后，让我们看看REST Controller层的 <code>OpenAIClientController</code> ，里面的两个客户端：</p><ul>
<li>
<p><code>chat</code> （ <code>/api/v1/chat</code> ）：POST调用，使用JSON格式的消息，接受一个问题，并输出响应内容。</p>
</li>
<li>
<p><code>createTranscription</code> （ <code>/api/v1/transcription</code> ）：POST调用，使用multipart/form-data，因为我们要上传音频文件，如 <code>m4a</code>和<code> mp3</code>等音频文件 ，API将把音频文件转录为文本。</p>
</li>
</ul><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>@</span><span>RestController</span></div></div><div><div><div>2</div></div><div><span>@</span><span>RequiredArgsConstructor</span></div></div><div><div><div>3</div></div><div><span>@</span><span>RequestMapping</span><span>(</span><span>value</span><span><span> </span><span>=</span><span> </span></span><span>"/api/v1"</span><span>)</span></div></div><div><div><div>4</div></div><div><span>public</span><span> </span><span>class</span><span><span> </span><span>OpenAIClientController</span><span> </span></span><span>{</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>    </span><span>private</span><span> </span><span>final</span><span> </span><span>OpenAIClientService</span><span> openAIClientService</span><span>;</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>@</span><span>PostMapping</span><span>(</span><span>value</span><span><span> </span><span>=</span><span> </span></span><span>"/chat"</span><span>, </span><span>consumes</span><span><span> </span><span>=</span><span> </span></span><span>MediaType</span><span>.</span><span>APPLICATION_JSON_VALUE</span><span>)</span></div></div><div><div><div>9</div></div><div><span>    </span><span>public</span><span> </span><span>ChatGPTResponse</span><span> </span><span>chat</span><span>(@</span><span>RequestBody</span><span> </span><span>ChatRequest</span><span><span> </span><span>chatRequest</span><span>){</span></span></div></div><div><div><div>10</div></div><div><span>        </span><span>return</span><span> </span><span>openAIClientService</span><span>.</span><span>chat</span><span>(chatRequest);</span></div></div><div><div><div>11</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span><span>    </span></span><span>@</span><span>PostMapping</span><span>(</span><span>value</span><span><span> </span><span>=</span><span> </span></span><span>"/transcription"</span><span>, </span><span>consumes</span><span><span> </span><span>=</span><span> </span></span><span>MediaType</span><span>.</span><span>MULTIPART_FORM_DATA_VALUE</span><span>)</span></div></div><div><div><div>14</div></div><div><span>    </span><span>public</span><span> </span><span>WhisperTranscriptionResponse</span><span> </span><span>createTranscription</span><span>(@</span><span>ModelAttribute</span><span> </span><span>TranscriptionRequest</span><span><span> </span><span>transcriptionRequest</span><span>){</span></span></div></div><div><div><div>15</div></div><div><span>        </span><span>return</span><span> </span><span>openAIClientService</span><span>.</span><span>createTranscription</span><span>(transcriptionRequest);</span></div></div><div><div><div>16</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>17</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section><section><h2>Authentication 认证<a href="#authentication-认证"><span>#</span></a></h2><p>OpenAI API调用需要传入一个带有API密钥的请求头“ <code>Authorization</code> “。让我们声明一个 <code>RequestInterceptor </code>bean来处理它，参见 <code>OpenAIClientConfig </code>类的示例代码片段：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>@</span><span>Bean</span></div></div><div><div><div>2</div></div><div><span>    </span><span>public</span><span> </span><span>RequestInterceptor</span><span> </span><span>apiKeyInterceptor</span><span>() {</span></div></div><div><div><div>3</div></div><div><span>        </span><span>return</span><span> request </span><span>-&gt;</span><span> </span><span>request</span><span>.</span><span>header</span><span>(</span><span>"Authorization"</span><span>, </span><span>"Bearer "</span><span><span> </span><span>+</span><span> apiKey);</span></span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>最后运行项目<a href="#最后运行项目"><span>#</span></a></h2><p>用Postman测试一下</p><p>下面是 <code>/chat</code> 客户端的示例请求/响应截图：</p><p><img src="https://thumbsnap.com/i/vymQU5KY.png" alt="" /></p><p>下面是 <code>/transcription</code> 的请求/响应：</p><p><img src="https://thumbsnap.com/i/NbWLi3MG.png" alt="" /></p><p>感觉对中文的识别准确度还是可以的。</p><p>使用Whisper模型的音频转录或翻译，文件上传目前限制为25 MB，并支持以下输入文件类型：<code>mp3</code>, <code>mp4</code>, <code>mpeg</code>, <code>mpga</code>, <code>m4a</code>, <code>wav</code>, <code>webm</code></p><p>Whisper型号的定价目前设定为0.006美元/分钟。</p></section><section><h2>使用Whisper API进行音频翻译<a href="#使用whisper-api进行音频翻译"><span>#</span></a></h2><p>除了音频转录，Whisper API还提供音频翻译端点 <code>/audio/translations</code> 。我也测试了这个特性。然而，我发现，尽管它说可以把音频翻译成英语，但我录了一个中文短语，并在应用程序中调用了 <code>/audio/translations</code> api，结果发现回应仍然是中文。我也用日语测试了这个api，它完整的翻译成了英语。这应该是一个bug。</p></section><section><h2>总结<a href="#总结"><span>#</span></a></h2><p>与任何第三方REST API一样，将OpenAI API集成到Springboot中非常简单。</p><p>因此使用api开发自己的人工智能程序会变的非常便捷。</p></section></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.wakaplus.com/posts/zk-ml/</id>
      <title type="text">零知识机器学习（ZKML）简介</title>
      <published>2023-09-17T00:00:00.000Z</published>
      <updated>2023-09-17T00:00:00.000Z</updated>
      <author><name>waka</name></author>
      <link rel="alternate" href="https://blog.wakaplus.com/posts/zk-ml/"/>
      <summary type="text">零知识机器学习（ZKML）简介</summary>
      <content type="html"><![CDATA[<section><h1>零知识机器学习（ZKML）简介<a href="#零知识机器学习zkml简介"><span>#</span></a></h1><p>Zero-Knowledge 机器学习（ZKML）是最近正在密码学界引起轰动的一个研究和开发领域。但它是什么，有什么用处呢？首先，让我们把这个术语分解成它的两个组成部分，并解释一下它们是什么。</p><section><h2>什么是 ZK？<a href="#什么是-zk"><span>#</span></a></h2><p>零知识证明是一种密码协议，其中一方（证明者）可以向另一方（验证者）证明一个给定的陈述是真实的，而不泄露除该陈述为真以外的任何附加信息。这是一个正在各个方面取得巨大进展的研究领域，涵盖了从研究到协议实施和应用的所有方面。</p><p>ZK 提供的两个主要“原语”（或者说构建块）是能够为一组给定的计算创建具有计算完整性证明的能力，其中证明比执行计算本身要容易地多。（我们称这种属性为“简洁性”）。ZK 证明也提供了隐藏计算中某些部分同时保持计算正确性的选项。（我们称这种属性为“零知识性”）。</p><p><strong>生成零知识证明需要非常大的计算量，大约比原始计算贵 100 倍</strong>。这意味着，在某些情况下由于最佳硬件上生成它们所需的时间使其不切实际，因此不能计算零知识证明。</p><p>然而，在近年来密码学、硬件和分布式系统领域的进步已经使零知识证明成为了越来越强大的计算可行的选择。这些进展已经为可以使用计算密集型证明的协议的创建提供了可能，从而扩大了新应用程序的设计空间。</p><p><strong>ZK 使用案例</strong></p><p>零知识密码学是 Web3 空间中最流行的技术之一，因为它允许开发人员构建可扩展和/或私有的应用程序。以下是一些实践中如何使用它的示例（尽管请注意，这些项目中许多都还在进行中）：</p><p><strong>1.通过 ZK rollups 扩展以太坊</strong></p><ul>
<li>Starknet</li>
<li>Scroll</li>
<li>Polygon Zero，Polygon Miden，Polygon zkEVM</li>
<li>zkSync</li>
</ul><p><strong>2.构建保护隐私的应用程序</strong></p><ul>
<li>Semaphore</li>
<li>MACI</li>
<li>Penumbra</li>
<li>Aztec Network</li>
</ul><p><strong>3.身份原语和数据来源</strong></p><ul>
<li>WorldID</li>
<li>Sismo</li>
<li>Clique</li>
<li>Axiom</li>
</ul><p><strong>4.第一层协议</strong></p><ul>
<li>Zcash</li>
<li>Mina</li>
</ul><p>随着 ZK 技术的成熟，我们相信将会出现新的应用程序的爆发，因为构建这些应用程序所使用的工具将需要更少的领域专业知识，对于开发人员来说将会更加容易使用。</p></section><section><h2>ZKML 的动机和当前的努力<a href="#zkml-的动机和当前的努力"><span>#</span></a></h2><p>我们生活在一个世界上，<strong>AI/ML 生成的内容越来越难以与人类生成的内容区分开来</strong>。零知识密码学将使我们能够做出这样的声明：“给定一段内容 C，它是由模型 M 应用于一些输入 X 生成的。”我们将能够验证某个输出是否是由大型语言模型（如 chatGPT）或文本到图像模型（如 DALL-E 2）等任何其他我们为其创建了零知识电路表示的模型所生成的。这些证明的零知识属性将使我们能够根据需要也隐藏输入或模型的某些部分。一个很好的例子是在一些敏感数据上应用机器学习模型，在不透露输入到第三方的情况下，用户可以知道他们的数据在模型推理后的结果（例如，在医疗行业）。</p><p>注：当我们谈论 ZKML 时，我们是指创建 ML 模型推理步骤的零知识证明，而不是关于 ML 模型训练（它本身已经非常计算密集）。目前，现有技术水平的零知识系统加上高性能硬件仍然相差几个数量级，无法证明当前可用的大型语言模型（LLMs）等庞大的模型，但是在创建较小模型的证明方面已经取得了一些进展。</p><p>我们对零知识密码学在为 ML 模型创建证明的上下文中的现有技术水平进行了一些研究，并创建了一个聚合相关研究、文章、应用程序和代码库的文章集。ZKML 的资源可以在 GitHub 上的 ZKML 社区的 <a href="https://github.com/zkml-community/awesome-zkml" target="_blank">awesome-zkml</a> 存储库中找到。</p><p><a href="https://www.moduluslabs.xyz/" target="_blank">Modulus Labs</a> 团队最近发布了一篇名为“<a href="https://medium.com/@ModulusLabs/chapter-5-the-cost-of-intelligence-da26dbf93307" target="_blank">智能的成本</a>”的论文，其中对现有的 ZK 证明系统进行了基准测试，并列举了不同大小的多个模型。目前，使用像 <a href="https://polygon.technology/blog/plonky2-a-deep-dive" target="_blank">plonky2 </a>这样的证明系统，在强大的 AWS 机器上运行 50 秒左右，可以为约 1800 万个参数的模型创建证明。以下是该论文中的一张图表：</p><p>另一个旨在改进 ZKML 系统技术水平的倡议是 Zkonduit 的 <a href="https://github.com/zkonduit/ezkl" target="_blank">ezkl</a> 库，它允许您创建对使用 ONNX 导出的 ML 模型的 ZK 证明。这使得任何 ML 工程师都能够为他们的模型的推理步骤创建 ZK 证明，并向任何正确实现的验证器证明输出。</p><p>有几个团队正在改进 ZK 技术，为 ZK 证明内部发生的操作创建优化硬件，并针对特定用例构建这些协议的优化实现。随着技术的成熟，更大的模型将在较不强大的机器上短时间内进行 ZK 证明。我们希望这些进展将使新的 ZKML 应用程序和用例得以出现。</p><p><img src="https://hx24-prod.mars-block.com/image/crawler/2023/04/06/1680766766002460.jpg" alt="" /></p></section><section><h2>潜在的使用案例<a href="#潜在的使用案例"><span>#</span></a></h2><p>为了确定 ZKML 是否适用于特定的应用，我们可以考虑 ZK 密码学的特性将如何解决与机器学习相关的问题。这可以用一个 Venn 图来说明：</p><p><img src="https://hx24-prod.mars-block.com/image/crawler/2023/04/06/1680766766002404.jpg" alt="" /></p><p>定义：</p><p><strong>1.Heuristic optimization</strong><a href="https://en.wikipedia.org/wiki/Heuristic_(computer_science)" target="_blank"><strong>启发式优化</strong></a>—— 一种问题解决方法，它使用经验法则或“启发式”来找到艰难的问题的好解决方案，而不是使用传统的优化方法。启发式优化方法旨在在相对的重要性和优化难度下，在合理的时间内找到好的或“足够好”的解决方案，而不是尝试找到最优解决方案。</p><p><strong>2.FHE ML</strong><a href="https://en.wikipedia.org/wiki/Homomorphic_encryption" target="_blank">全同态加密</a>—— 完全同态加密ML允许开发人员以保护隐私的方式训练和评估模型；然而，与ZK证明不同，没有办法通过密码学方式证明所执行的计算的正确性。</p><ul>
<li>像 <a href="https://www.zama.ai/" target="_blank">Zama.ai</a>这样的团队正在从事这个领域的工作。</li>
</ul><p><strong>3.ZK vs Validity</strong> —— 在行业中，这些术语通常被互换使用，因为有效性证明是ZK证明，不会隐藏计算或其结果的某些部分。在ZKML的上下文中，大多数当前的应用程序都利用了ZK证明的有效性证明方面。</p><p><strong>4.Validity ML</strong> —— ZK证明ML模型，在其中没有计算或结果被保密。它们证明计算的正确性。</p><p>以下是一些潜在的 ZKML 用例示例：</p><p>1.计算完整性（有效性 ML）</p><ul>
<li>Modulus Labs<a href="https://www.moduluslabs.xyz/" target="_blank">模量实验室</a></li>
<li>基于链上可验证的 ML 交易机器人 - <a href="https://github.com/Modulus-Labs/RockyBot" target="_blank">RockyBot</a></li>
</ul><p>自我改进视觉区块链（示例）：</p><ul>
<li>增强 Lyra 金融期权协议 AMM 的智能特性<a href="https://www.lyra.finance/" target="_blank">Lyra 金融期权协议 AMM</a></li>
<li>为 <a href="https://www.astraly.xyz/" target="_blank">Astraly</a>  创建透明的基于 AI 的声誉系统（ZK oracle）</li>
<li>使用<a href="https://aztec.network/" target="_blank"> ML for Aztec Protocol</a>（具有隐私功能的 zk-rollup）致力于合同级合规工具所需的技术突破。</li>
</ul><p>2.<a href="https://twitter.com/daniel_d_kang/status/1582519854405255168?s=20&amp;t=16FXZixQvD5G_B9IFVzmaA" target="_blank">机器学习即服务</a>(MLaaS) 透明；</p><p>3.ZK 异常/欺诈检测：</p><ul>
<li>这种应用场景使得可创建针对<a href="https://blog.trailofbits.com/2020/05/21/reinventing-vulnerability-disclosure-using-zero-knowledge-proos/" target="_blank">允许为可利用性</a>/欺诈的 ZK 证明成为可能。异常检测模型可以在智能合约数据上进行训练，并由 DAOs 同意作为有趣的度量标准，以便能够自动化安全程序，如更主动、预防性地暂停合约。已有初创企业正在研究在智能合约环境中使用 ML 模型进行安全目的的方法，因此 ZK 异常检测证明似乎是自然的下一步。</li>
</ul><p>4.ML 推理的通用有效性证明：能够轻松证明和验证输出是给定模型和输入对的乘积。</p><p>5.隐私 (ZKML)。</p><p>6.去中心化的 <a href="https://www.kaggle.com/" target="_blank">Kaggle</a>：证明模型在某些测试数据上的准确率大于 x%，而不会显示权重。</p><p>7.隐私保护推理：将对私人患者数据的医疗诊断输入模型，并将敏感的推理（例如，癌症测试结果）发送给患者。（来源：<a href="https://eprint.iacr.org/2020/584.pdf" target="_blank">vCNN 论文</a>，第 2/16 页）</p><p>8.Worldcoin：</p><ul>
<li><strong>IrisCode 的可升级性</strong>：<a href="https://id.worldcoin.org/" target="_blank">World ID</a> 用户将能够在他们的移动设备的加密存储中自我保管其生物特征，下载用于生成 IrisCode 的 ML 模型并在本地创建零知识证明，以证明其 IrisCode 已成功创建。这个 IrisCode 可以被无需许可地插入注册的 Worldcoin 用户之一，因为接收的智能合约可以验证零知识证明，从而验证 IrisCode 的创建。这意味着，如果 Worldcoin 将来升级机器学习模型以一种破坏与其之前版本兼容性的方式创建 IrisCode，用户就不必再次去 Orb，而可以在设备上本地创建这个零知识证明。</li>
<li><strong>Orb 安全性</strong>：目前，Orb 在其受信任的环境中执行几个欺诈和篡改检测机制。然而，我们可以创建一个零知识证明，表明这些机制在拍摄图像和生成 IrisCode 时是活动的，以便为 Worldcoin 协议提供更好的活体保证，因为我们可以完全确定这些机制在整个 IrisCode 生成过程中都将运行。</li>
</ul><p>总之，ZKML 技术有着广泛的应用前景，并且正在快速发展。随着越来越多的团队和个人加入到这个领域，我们相信 ZKML 的应用场景将会更加多样化和广泛化。</p></section></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.wakaplus.com/posts/merkle-go/</id>
      <title type="text">默克尔树(Merkle tree)的Go实现</title>
      <published>2023-08-23T00:00:00.000Z</published>
      <updated>2023-08-23T00:00:00.000Z</updated>
      <author><name>waka</name></author>
      <link rel="alternate" href="https://blog.wakaplus.com/posts/merkle-go/"/>
      <summary type="text">使用go语言实现默克尔树</summary>
      <content type="html"><![CDATA[<section><h3>Merkle trees<a href="#merkle-trees"><span>#</span></a></h3><hr /><p>Merkle树是区块链技术的基本组成部分。它是由不同数据块的散列组成的数学数据结构，用作块中所有交易的摘要。</p><p>它还允许对大量数据中的内容进行有效和安全的验证。此结构有助于验证数据的一致性和内容。比特币和以太坊都使用Merkle树结构。Merkle树也被称为哈希树。</p><p>从根本上说，Merkle树是数据结构树，其中每个叶节点都用数据块的哈希标记，非叶节点用加密标记 其子节点标签的哈希值。叶节点是树中的最低节点。</p><p></p><figure><img src="https://img.learnblockchain.cn/attachments/2023/02/R9Yl2Xvj63e26ec555cc7.png" alt="图片.png" /><figcaption>图片.png</figcaption></figure><p></p></section>
<section><h3>原理<a href="#原理"><span>#</span></a></h3><p>区块链中每个区块都会有一个 Merkle 树，它从叶子节点（树的底部）开始，一个叶子节点就是一个交易哈希。叶子节点的数量必须是双数，但是并非每个块都包含了双数的交易。如果一个块里面的交易数为单数，那么就将最后一个叶子节点（也就是 Merkle 树的最后一个交易，不是区块的最后一笔交易）复制一份凑成双数。</p><p>从下往上，两两成对，连接两个节点哈希，将组合哈希作为新的哈希。新的哈希就成为新的树节点。重复该过程，直到仅有一个节点，也就是树根。根哈希然后就会当作是整个块交易的唯一标示，将它保存到区块头，然后用于工作量证明。</p><hr /></section>
<section><h3>代码实现<a href="#代码实现"><span>#</span></a></h3><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>package main</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>import (</span></div></div><div><div><div>4</div></div><div><span><span>  </span></span><span>"bufio"</span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>"crypto/sha256"</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>"fmt"</span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>"os"</span></div></div><div><div><div>8</div></div><div><span><span>  </span></span><span>"strconv"</span></div></div><div><div><div>9</div></div><div><span>)</span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span>//默克尔树节点结构体</span></div></div><div><div><div>12</div></div><div><span>type Node struct {</span></div></div><div><div><div>13</div></div><div><span><span>  </span></span><span>Index    int</span></div></div><div><div><div>14</div></div><div><span><span>  </span></span><span>Value    string</span></div></div><div><div><div>15</div></div><div><span><span>  </span></span><span>RootTree *MHTree</span></div></div><div><div><div>16</div></div><div><span>}</span></div></div><div><div><div>17</div></div><div>
</div></div><div><div><div>18</div></div><div><span>//默克尔树结构体</span></div></div><div><div><div>19</div></div><div><span>type MHTree struct {</span></div></div><div><div><div>20</div></div><div><span><span>  </span></span><span>Length   int</span></div></div><div><div><div>21</div></div><div><span><span>  </span></span><span>Nodes    []Node</span></div></div><div><div><div>22</div></div><div><span><span>  </span></span><span>rootHash string</span></div></div><div><div><div>23</div></div><div><span>}</span></div></div><div><div><div>24</div></div><div>
</div></div><div><div><div>25</div></div><div><span>//获得默克尔树根节点哈希值</span></div></div><div><div><div>26</div></div><div><span>func (t *MHTree) GetRootHash() string {</span></div></div><div><div><div>27</div></div><div><span><span>  </span></span><span>//不管是否存储，都重新计算哈希值</span></div></div><div><div><div>28</div></div><div><span><span>  </span></span><span>t.rootHash =   t.Nodes[1].getNodeHash()</span></div></div><div><div><div>29</div></div><div><span><span>  </span></span><span>return t.rootHash</span></div></div><div><div><div>30</div></div><div><span>}</span></div></div><div><div><div>31</div></div><div>
</div></div><div><div><div>32</div></div><div><span>//计算默克尔树中某个节点的哈希值</span></div></div><div><div><div>33</div></div><div><span>func (n *Node) getNodeHash() string {</span></div></div><div><div><div>34</div></div><div><span><span>  </span></span><span>//叶子节点，则直接计算该节点Value的哈希值</span></div></div><div><div><div>35</div></div><div><span><span>  </span></span><span>if n.Value != "" {</span></div></div><div><div><div>36</div></div><div><span><span>    </span></span><span>return calDataHash(n.Value)</span></div></div><div><div><div>37</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>38</div></div><div><span><span>  </span></span><span>//非叶子节点，则递归计算哈希值,其为2个子节点哈希值的哈希值123123123123</span></div></div><div><div><div>39</div></div><div><span><span>    </span></span><span>return calDataHash(n.RootTree.Nodes[n.Index*2].getNodeHash() + n.RootTree.Nodes[n.Index*2+1].getNodeHash()</span></div></div><div><div><div>40</div></div><div><span><span>        </span></span><span>)</span></div></div><div><div><div>41</div></div><div>
</div></div><div><div><div>42</div></div><div><span>//计算数据的哈希值</span></div></div><div><div><div>43</div></div><div><span>func calDataHash(data string) string {</span></div></div><div><div><div>44</div></div><div><span><span>  </span></span><span>hash := sha256.New()</span></div></div><div><div><div>45</div></div><div><span><span>  </span></span><span>hash.Write([]byte(data))</span></div></div><div><div><div>46</div></div><div><span><span>  </span></span><span>return string(hash.Sum(nil))</span></div></div><div><div><div>47</div></div><div><span>}</span></div></div><div><div><div>48</div></div><div>
</div></div><div><div><div>49</div></div><div><span>//从结构化文件创建默克尔树</span></div></div><div><div><div>50</div></div><div><span>func CreateMHTree(fileName string) MHTree {</span></div></div><div><div><div>51</div></div><div><span><span>  </span></span><span>var tree MHTree</span></div></div><div><div><div>52</div></div><div><span><span>  </span></span><span>//打开文件</span></div></div><div><div><div>53</div></div><div><span><span>  </span></span><span>file, err := os.Open(fileName)</span></div></div><div><div><div>54</div></div><div><span><span>  </span></span><span>if err != nil {</span></div></div><div><div><div>55</div></div><div><span><span>    </span></span><span>panic(err)</span></div></div><div><div><div>56</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>57</div></div><div><span><span>  </span></span><span>defer file.Close()</span></div></div><div><div><div>58</div></div><div><span><span>  </span></span><span>//获取读取器</span></div></div><div><div><div>59</div></div><div><span><span>  </span></span><span>buf := bufio.NewReader(file)</span></div></div><div><div><div>60</div></div><div><span><span>  </span></span><span>//读取首行，获得叶子节点数目（要求叶子节点数目为2的整数次幂</span></div></div><div><div><div>61</div></div><div><span><span>  </span></span><span>dataCountStr, _, _ := buf.ReadLine()</span></div></div><div><div><div>62</div></div><div><span><span>  </span></span><span>dataCount, _ := strconv.Atoi(string(dataCountStr))</span></div></div><div><div><div>63</div></div><div>
</div></div><div><div><div>64</div></div><div><span><span>  </span></span><span>//判断幂次</span></div></div><div><div><div>65</div></div><div><span><span>  </span></span><span>level := 0</span></div></div><div><div><div>66</div></div><div><span><span>  </span></span><span>for i := 1; ; i++ {</span></div></div><div><div><div>67</div></div><div><span><span>    </span></span><span>if 2&lt;&lt;i == dataCount {</span></div></div><div><div><div>68</div></div><div><span><span>      </span></span><span>level = i</span></div></div><div><div><div>69</div></div><div><span><span>      </span></span><span>break</span></div></div><div><div><div>70</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>71</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>72</div></div><div><span><span>  </span></span><span>//创建默克尔树</span></div></div><div><div><div>73</div></div><div>
</div></div><div><div><div>74</div></div><div><span><span>  </span></span><span>//给非叶子节点赋值</span></div></div><div><div><div>75</div></div><div><span><span>  </span></span><span>for i := 1; i &lt;= 2&lt;&lt;level-1; i++ {</span></div></div><div><div><div>76</div></div><div><span><span>    </span></span><span>tree.Nodes[i].Index = i</span></div></div><div><div><div>77</div></div><div><span><span>    </span></span><span>tree.Nodes[i].RootTree = &amp;tree</span></div></div><div><div><div>78</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>79</div></div><div><span><span>  </span></span><span>//读取文件数据，给叶子节点赋值</span></div></div><div><div><div>80</div></div><div><span><span>  </span></span><span>for i := 2 &lt;&lt; level; i &lt; tree.Length; i++ {</span></div></div><div><div><div>81</div></div><div><span><span>    </span></span><span>str, _, _ := buf.ReadLine()</span></div></div><div><div><div>82</div></div><div><span><span>    </span></span><span>tree.Nodes[i].Index = i</span></div></div><div><div><div>83</div></div><div><span><span>    </span></span><span>tree.Nodes[i].RootTree = &amp;tree</span></div></div><div><div><div>84</div></div><div><span><span>    </span></span><span>tree.Nodes[i].Value = string(str)</span></div></div><div><div><div>85</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>86</div></div><div><span><span>  </span></span><span>return tree</span></div></div><div><div><div>87</div></div><div><span>}</span></div></div><div><div><div>88</div></div><div>
</div></div><div><div><div>89</div></div><div><span>func main() {</span></div></div><div><div><div>90</div></div><div><span><span>  </span></span><span>var fileName string</span></div></div><div><div><div>91</div></div><div>
</div></div><div><div><div>92</div></div><div><span><span>  </span></span><span>fmt.Println("请输入原始数据文件名称")</span></div></div><div><div><div>93</div></div><div><span><span>  </span></span><span>fmt.Scanln(&amp;fileName)</span></div></div><div><div><div>94</div></div><div><span><span>  </span></span><span>mhTree1 := CreateMHTree(fileName)</span></div></div><div><div><div>95</div></div><div><span><span>  </span></span><span>fmt.Println("请输入比对数据文件名称")</span></div></div><div><div><div>96</div></div><div><span><span>  </span></span><span>fmt.Scanln(&amp;fileName)</span></div></div><div><div><div>97</div></div><div><span><span>  </span></span><span>mhTree2 := CreateMHTree(fileName)</span></div></div><div><div><div>98</div></div><div>
</div></div><div><div><div>99</div></div><div><span><span>  </span></span><span>hash1 := mhTree1.GetRootHash()</span></div></div><div><div><div>100</div></div><div><span><span>  </span></span><span>hash2 := mhTree2.GetRootHash()</span></div></div><div><div><div>101</div></div><div>
</div></div><div><div><div>102</div></div><div><span><span>  </span></span><span>if hash1 == hash2 {</span></div></div><div><div><div>103</div></div><div><span><span>    </span></span><span>fmt.Println("用户没有改变数据")</span></div></div><div><div><div>104</div></div><div><span><span>  </span></span><span>} else {</span></div></div><div><div><div>105</div></div><div><span><span>    </span></span><span>fmt.Println("用户改变了数据")</span></div></div><div><div><div>106</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>107</div></div><div>
</div></div><div><div><div>108</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.wakaplus.com/posts/zk/</id>
      <title type="text">零知识学习资料汇总</title>
      <published>2023-08-23T00:00:00.000Z</published>
      <updated>2023-08-23T00:00:00.000Z</updated>
      <author><name>waka</name></author>
      <link rel="alternate" href="https://blog.wakaplus.com/posts/zk/"/>
      <summary type="text">收集相关的零知识学习资料</summary>
      <content type="html"><![CDATA[<section><h1>零知识证明学习资源汇总<a href="#零知识证明学习资源汇总"><span>#</span></a></h1><p>零知识证明技术是现代密码学三大基础之一，由 S.Goldwasser、S.Micali 及 C.Rackoff 在 20 世纪 80 年代初提出。早期的零知识证明由于其效率和可用性等限制，未得到很好的利用，仅停留在理论层面。直到近年来，零知识证明的理论研究才开始不断突破，同时区块链也为零知识证明创造了大展拳脚的机会，因而走进大众视野。</p><p>零知识证明这项“黑科技”随着它的热度逐渐增加，相关的学习资源也慢慢丰富起来了。但是由于，一方面零知识证明背后的原理颇为复杂，且内容繁多；另一方面，针对零知识证明的学习资源质量参差不齐，尚未形成系统。因此对绝大多数读者来说，学习零知识证明的难度依然很大。</p><p>本文收集了关于零知识证明的一些学习资料（包括科普文章，论文，开源仓库及相关学习网站等），并对这些资源进行了整理分析，希望能对大家有所帮助。</p><p><em>由于整理时间有限和笔者自身知识的局限性，文章存在不足之处，欢迎纠正、补充和探讨。</em></p><section><h2>1. 故事中的零知识证明<a href="#1-故事中的零知识证明"><span>#</span></a></h2><p>初次接触零知识证明的小伙伴一定会问，究竟什么是零知识证明呢？它到底在做什么？</p><p>推荐几篇适合小白的文章：</p><ul>
<li>
<p><strong>「推荐文章一」<a href="https://medium.com/qed-it/the-incredible-machine-4d1270d7363a" target="_blank">一个数独引发的惨案：零知识证明（Zero-Knowledge Proof）</a></strong></p>
<p>推荐值：❤️❤️❤️❤️❤️</p>
<p>难度值：⭐️</p>
<p>这篇文章的作者是著名的 Ghost 和 Spectre 这两个协议的创始团队的领队 Aviv Zohar。文章非常接地气且通俗易懂，通过三个好朋友一起玩数独游戏的故事介绍了什么是零知识证明。</p>
<p>原文链接：<a href="https://medium.com/qed-it/the-incredible-machine-4d1270d7363a" target="_blank">https://medium.com/qed-it/the-incredible-machine-4d1270d7363a</a></p>
<p>中文翻译：<a href="https://zhuanlan.zhihu.com/p/34072069" target="_blank">https://zhuanlan.zhihu.com/p/34072069</a></p>
<p>另外这篇文章中引用了两篇介绍零知识证明的论文，也值得看一看。</p>
</li>
<li>
<p><strong>「推荐文章二」<a href="http://pages.cs.wisc.edu/~mkowalcz/628.pdf" target="_blank">How to explain zero-knowledge protocols to your children</a></strong></p>
<p>推荐值：❤️❤️❤️</p>
<p>难度值：⭐️</p>
<p>这篇来自上个世纪的文章，正如它的标题一样，作者以给孩子讲故事的口吻，讲了一个阿里巴巴与四十大盗的故事，这个故事后来也成为了介绍零知识证明的经典故事。以故事的形式讲述零知识证明使得这篇文章理解起来也很简单。</p>
<p>原文链接：<a href="http://pages.cs.wisc.edu/~mkowalcz/628.pdf" target="_blank">http://pages.cs.wisc.edu/~mkowalcz/628.pdf</a></p>
<p>中文翻译：<a href="https://blog.dreamerryao.wiki/archives/%E8%AF%91howtoexplainzero-knowledgeprotocolstoyourchildren" target="_blank">https://blog.dreamerryao.wiki/archives/%E8%AF%91howtoexplainzero-knowledgeprotocolstoyourchildren</a></p>
</li>
<li>
<p><strong>「推荐文章三」<a href="http://www.wisdom.weizmann.ac.il/~naor/PAPERS/sudoku.pdf" target="_blank">Cryptographic and Physical Zero-Knowledge Proof Systems for Solutions of Sudoku Puzzles</a></strong></p>
<p>推荐值：❤️❤️❤️</p>
<p>难度值：⭐️⭐️⭐️</p>
<p>如何在不泄漏任何信息的前提下向别人证明你有一个数独问题的答案呢？同样这个问题也是介绍零知识证明的经典案例。论文中提出了使用一个零知识证明协议解决这个问题的方案，这篇论文相比较于前两篇文章，理论性更强一些，篇幅更长，协议的介绍更为详细，但总体来说还算比较好理解。</p>
<p>原文链接：<a href="http://www.wisdom.weizmann.ac.il/~naor/PAPERS/sudoku.pdf" target="_blank">http://www.wisdom.weizmann.ac.il/~naor/PAPERS/sudoku.pdf</a></p>
</li>
<li>
<p><strong>「推荐文章四」<a href="https://medium.com/hackernoon/zero-knowledge-proofs-a-tale-of-two-friends-d7a0ffac3185" target="_blank">Zero knowledge proofs: a tale of two friends</a></strong></p>
<p>推荐值：❤️❤️</p>
<p>难度值：⭐️⭐️</p>
<p>与前面几篇文章类似，这篇文章也是通过讲故事的形式来向读者介绍零知识证明的。文中 Prover 要向 Verifier 证明其知道魔法的解法。这篇文章篇幅较短，内容理解起来难度较小。</p>
<p>原文链接：<a href="https://medium.com/hackernoon/zero-knowledge-proofs-a-tale-of-two-friends-d7a0ffac3185" target="_blank">https://medium.com/hackernoon/zero-knowledge-proofs-a-tale-of-two-friends-d7a0ffac3185</a></p>
</li>
<li>
<p><strong>「推荐文章五」<a href="https://medium.com/hackernoon/eli5-zero-knowledge-proof-78a276db9eff" target="_blank">Explain Like I’m 5: Zero Knowledge Proof (Halloween Edition)</a></strong></p>
<p>推荐值：❤️❤️</p>
<p>难度值：⭐️⭐️</p>
<p>这同样是一篇讲故事的文章，哈哈~</p>
<p>这篇文章讲述了一个糖果和百万富翁的故事（Candy bars and millionaires），文章同样篇幅较短，内容理解起来难度较小。</p>
<p>原文链接：<a href="https://medium.com/hackernoon/eli5-zero-knowledge-proof-78a276db9eff" target="_blank">https://medium.com/hackernoon/eli5-zero-knowledge-proof-78a276db9eff</a></p>
</li>
</ul><p>兴许是因为如何解释零知识证明的问题并不简单，所以绝大部分入门级的科普文章都是从讲故事开始的。</p></section><section><h2>2. 深入理解零知识证明<a href="#2-深入理解零知识证明"><span>#</span></a></h2><p>零知识证明技术涉及的知识点繁多，性质也各不相同。了解了什么是零知识证明以后，就需要对零知识证明更深刻的理解，推荐以下几篇零知识证明系列科普文。</p><ul>
<li>
<p><strong>「推荐文章六」零知识证明: 抛砖引玉</strong></p>
<p>推荐值：❤️❤️❤️❤️</p>
<p>难度值：⭐️⭐️⭐️</p>
<p>作者是 Zerocash 协议的创建者之一，密码学大神 Matthew Green[1]。这两篇文章几乎涵盖了学习零知识证明原理所有的基本概念，文章思路很清晰。</p>
<ul>
<li>
<p><a href="https://ethfans.org/posts/zero-knowledge-proofs-illustrated-primerhttps://blog.cryptographyengineering.com/2014/11/27/zero-knowledge-proofs-illustrated-primer/" target="_blank">零知识证明: 抛砖引玉</a></p>
<p>第一篇文章主要从零知识证明的起源开始讲起，然后同样借助了地图三染色和 “时光机”来对零知识证明进行介绍。</p>
<p>原文链接：<a href="https://blog.cryptographyengineering.com/2014/11/27/zero-knowledge-proofs-illustrated-primer/" target="_blank">https://blog.cryptographyengineering.com/2014/11/27/zero-knowledge-proofs-illustrated-primer/</a></p>
<p>中文翻译版本：<a href="https://ethfans.org/posts/zero-knowledge-proofs-illustrated-primer" target="_blank">https://ethfans.org/posts/zero-knowledge-proofs-illustrated-primer</a></p>
</li>
<li>
<p><a href="https://blog.cryptographyengineering.com/2017/01/21/zero-knowledge-proofs-an-illustrated-primer-part-2/" target="_blank">零知识证明：抛砖引玉，Part-2</a></p>
<p>这篇文章在第一篇的基础上，进一步对零知识证明的三个性质：可靠性，完整性和零知识，展开介绍。另外还结合 Schnorr 协议介绍了交互式和非交互式的概念。</p>
<p>原文链接：<a href="https://blog.cryptographyengineering.com/2017/01/21/zero-knowledge-proofs-an-illustrated-primer-part-2/" target="_blank">https://blog.cryptographyengineering.com/2017/01/21/zero-knowledge-proofs-an-illustrated-primer-part-2/</a></p>
<p>中文翻译版本：<a href="https://ethfans.org/posts/zero-knowledge-proofs-an-illustrated-primer-part-2" target="_blank">https://ethfans.org/posts/zero-knowledge-proofs-an-illustrated-primer-part-2</a></p>
</li>
</ul>
</li>
<li>
<p><strong>「推荐文章七」安比实验室零知识证明介绍系列文章</strong></p>
<p>推荐值：❤️❤️❤️❤️❤️</p>
<p>难度值：⭐️⭐️⭐️</p>
<p>这个系列的作者是安比实验室创始人郭宇，文章与以往的零知识证明科普文章的不同之处就是它没有单独去讲解零知识的基本原理。而且结合更多的概念和原理，更透彻得将零知识证明技术涉及得诸多原理逐一进行讲解，文章专业性较强，还包含了作者大量的思考，但理解起来也较为直观易懂，非常适合想要深入理解零知识证明的小伙伴。</p>
<p>另外这个系列的文章还在持续更新中。</p>
<ul>
<li>
<p><a href="https://sec-bit.github.io/blog/2019/07/31/zero-knowledge-and-proof/" target="_blank">探索零知识证明系列一：初识「零知识」与「证明」</a></p>
<p>作为系列的第一篇，这篇文章首先介绍了「证明」的发展历程和「零知识」的作用，并举了一个地图三染色的例子，然后又对「信息」、「知识」和可满足电路的概念展开了介绍。</p>
<p>原文链接：<a href="https://sec-bit.github.io/blog/2019/07/31/zero-knowledge-and-proof/" target="_blank">https://sec-bit.github.io/blog/2019/07/31/zero-knowledge-and-proof/</a></p>
</li>
<li>
<p><a href="https://sec-bit.github.io/blog/2019/08/06/understand-zero-knowledge-proof-by-simulator/" target="_blank">探索零知识证明系列二：从「模拟」理解零知识证明：平行宇宙与时光倒流</a></p>
<p>这篇文章介绍了零知识证明中的一个非常重要的概念——模拟（Simulator），「模拟」可以说是安全协议中核心的核心。文章中借助 “平行世界” 的假设去理解零知识读起来也非常有意思。</p>
<p>原文链接：<a href="https://sec-bit.github.io/blog/2019/08/06/understand-zero-knowledge-proof-by-simulator/" target="_blank">https://sec-bit.github.io/blog/2019/08/06/understand-zero-knowledge-proof-by-simulator/</a></p>
</li>
<li>
<p><a href="https://sec-bit.github.io/blog/2019/08/28/extractor-and-proof-of-knowledge/" target="_blank">探索零知识证明系列三：读心术：从零知识证明中提取「知识」</a></p>
<p>零知识证明有三个重要的性质：可靠性，完整性和零知识。这篇文章探讨了可靠性。文章解释了如何借助「抽取器」和时间倒流的超能力把 Alice 的「知识」完整地「抽取」出来，并可给出了一个与之相关攻击实例 —— ECDSA 签名攻击。</p>
<p>原文链接：<a href="https://sec-bit.github.io/blog/2019/08/28/extractor-and-proof-of-knowledge/" target="_blank">https://sec-bit.github.io/blog/2019/08/28/extractor-and-proof-of-knowledge/</a></p>
</li>
<li>
<p><a href="https://sec-bit.github.io/blog/2019/11/01/from-interactive-zkp-to-non-interactive-zkp/" target="_blank">探索零知识证明系列四：亚瑟王的「随机」挑战：从交互到非交互式零知识证明</a></p>
<p>这篇文章主要在讲零知识证明的信任根基——随机挑战。文章对零知识证明协议在两种不同的形式（交互式和非交互式）下随机挑战的方式进行了介绍。另外文章还对交互和非交互形式展开了介绍。</p>
<p>原文链接：<a href="https://sec-bit.github.io/blog/2019/11/01/from-interactive-zkp-to-non-interactive-zkp/" target="_blank">https://sec-bit.github.io/blog/2019/11/01/from-interactive-zkp-to-non-interactive-zkp/</a></p>
</li>
</ul>
</li>
<li>
<p><strong>「推荐文章八」<a href="https://zhuanlan.zhihu.com/p/29491567" target="_blank">零知识证明：一个略微严肃的科普</a></strong></p>
<p>推荐值：❤️❤️❤️</p>
<p>难度值：⭐️⭐️⭐️⭐️</p>
<p>邓老师这篇“略微严肃”的科普，主要涉及两部分：1. 交互式证明的巨大威力；2. 零知识证明的定义和那些广泛流传的错误的例子</p>
<p>原文链接：<a href="https://zhuanlan.zhihu.com/p/29491567" target="_blank">https://zhuanlan.zhihu.com/p/29491567</a></p>
</li>
<li>
<p><strong>「推荐文章九」<a href="https://blog.aventus.io/zero-knowledge-proofs-a-laymans-introduction-7020b93beeda" target="_blank">Zero-Knowledge Proofs: A Layman’s Introduction</a></strong></p>
<p>推荐值：❤️❤️</p>
<p>难度值：⭐️⭐️</p>
<p>这篇文章首先介绍了零知识证明协议中的三个参与者（Creator，Prover，Verifier）以及 Proofs 和 Verification的概念，并对 zkSNARK （一类零知识证明协议）和椭圆曲线的相关资料进行了介绍。</p>
<p>原文链接：<a href="https://blog.aventus.io/zero-knowledge-proofs-a-laymans-introduction-7020b93beeda" target="_blank">https://blog.aventus.io/zero-knowledge-proofs-a-laymans-introduction-7020b93beeda</a></p>
</li>
<li>
<p><strong>「推荐文章十」<a href="https://zhuanlan.zhihu.com/p/33189921" target="_blank">白话零知识证明（一）</a></strong></p>
<p>推荐值：❤️❤️</p>
<p>难度值：⭐️⭐️</p>
<p>这篇来自秘猿科技的文章通过阿里巴巴的故事引出了零知识证明的一些概念，并对其进行了介绍。</p>
<p>原文链接：<a href="https://zhuanlan.zhihu.com/p/33189921" target="_blank">https://zhuanlan.zhihu.com/p/33189921</a></p>
</li>
</ul><p>零知识证明涉及很多很有意思的思想和原理，都很值得探讨。在此不得不感叹于数学与密码学的精妙之处，也不得不钦佩密码学家们的厉害。</p></section><section><h2>3. 零知识证明的发展<a href="#3-零知识证明的发展"><span>#</span></a></h2><p>零知识证明的研究今年来一直有新的进展，密码学家们提出了各种不同的协议，推荐两篇文章介绍零知识证明研究的发展过程。</p><ul>
<li>
<p><strong>「推荐文章十一」<a href="https://zhuanlan.zhihu.com/p/31651393" target="_blank">区块链学习笔记 (1)：零知识证明的江湖</a></strong></p>
<p>推荐值：❤️❤️❤️</p>
<p>难度值：⭐️⭐️</p>
<p>这篇文章讲了自 1895 年提出以来，零知识证明理论研究的发展过程，以及 zk-SNARKs 与零知识证明技术结合起来的发展过程。推荐给想了解零知识理论研究的发展过程的小伙伴。</p>
<p>原文链接：<a href="https://zhuanlan.zhihu.com/p/31651393" target="_blank">https://zhuanlan.zhihu.com/p/31651393</a></p>
</li>
<li>
<p><strong>「推荐文章十二」<a href="https://www.benthamsgaze.org/2019/05/22/efficient-cryptographic-arguments-and-proofs-or-how-i-became-a-fractional-monetary-unit/" target="_blank">Efficient Cryptographic Arguments and Proofs – Or How I Became a Fractional Monetary Unit</a></strong></p>
<p>推荐值：❤️❤️❤️</p>
<p>难度值：⭐️⭐️</p>
<p>这篇文章来自UCL信息安全研究人员的博客 <a href="https://www.benthamsgaze.org/about/" target="_blank">Bentham’s Gaze</a>[2],文章介绍了自零知识证明提出以来，这群研究人员在理论研究上的研究历程及成果，包括知名的 bulletProof 和 zk-STARK 等。读完这篇文章相信会对大家深入理解零知识证明的诸多协议有所帮助。</p>
<p>原文链接：<a href="https://www.benthamsgaze.org/2019/05/22/efficient-cryptographic-arguments-and-proofs-or-how-i-became-a-fractional-monetary-unit/" target="_blank">https://www.benthamsgaze.org/2019/05/22/efficient-cryptographic-arguments-and-proofs-or-how-i-became-a-fractional-monetary-unit/</a></p>
</li>
</ul><p>零知识证明迄今为止发展了三十多年，早期一直停留在理论层面，直到近十年才逐渐取得突破。随着越来越多研究人员的进场，相信这个领域未来还会有更多令人惊喜的成果。</p></section><section><h2>4. zk-SNARKs 原理<a href="#4-zk-snarks-原理"><span>#</span></a></h2><p>作为零知识证明领域最知名的一类协议，zk-SNARKs 的理论研究和应用也最为广泛。推荐一些介绍 zk-SNARKs 的资料。</p><ul>
<li>
<p><strong>「推荐文章十三」V 神的 zk-SNARKs 科普文章</strong></p>
<p>推荐值：❤️❤️❤️❤️</p>
<p>难度值：⭐️⭐️⭐️⭐️</p>
<p>V 神的这几篇文章应该算得上是流传最为广泛的 zk-SNARK 科普文了。不用多说，推荐阅读。</p>
<ul>
<li>
<p><a href="https://medium.com/@VitalikButerin/quadratic-arithmetic-programs-from-zero-to-hero-f6d558cea649" target="_blank">Quadratic Arithmetic Programs: from Zero to Hero</a></p>
<p>这篇文章详细介绍了 zk-SNARKs 的实现过程。文中将 zk-SNARKs 的实现分为以下几个步骤：</p>
<ol>
<li>computational problem —&gt; 电路</li>
<li>电路 —&gt; R1CS</li>
<li>R1CS —&gt; QAP</li>
<li>QAP —&gt; Linear PCP</li>
<li>Linear PCP —&gt; Linear Interactive Proof</li>
<li>Linear Interactive Proof —&gt; zkSNARK</li>
</ol>
<p>原文链接：<a href="https://medium.com/@VitalikButerin/quadratic-arithmetic-programs-from-zero-to-hero-f6d558cea649" target="_blank">https://medium.com/@VitalikButerin/quadratic-arithmetic-programs-from-zero-to-hero-f6d558cea649</a></p>
</li>
<li>
<p><a href="https://medium.com/@VitalikButerin/exploring-elliptic-curve-pairings-c73c1864e627" target="_blank">Exploring Elliptic Curve Pairings</a></p>
<p>这篇文章介绍了椭圆曲线配对。</p>
<p>原文链接：<a href="https://medium.com/@VitalikButerin/exploring-elliptic-curve-pairings-c73c1864e627" target="_blank">https://medium.com/@VitalikButerin/exploring-elliptic-curve-pairings-c73c1864e627</a></p>
</li>
<li>
<p><a href="https://medium.com/@VitalikButerin/zk-snarks-under-the-hood-b33151a013f6" target="_blank">Zk-SNARKs: Under the Hood</a></p>
<p>这篇文章主要介绍了匹诺曹协议。</p>
<p>原文链接：<a href="https://medium.com/@VitalikButerin/zk-snarks-under-the-hood-b33151a013f6" target="_blank">https://medium.com/@VitalikButerin/zk-snarks-under-the-hood-b33151a013f6</a></p>
</li>
</ul>
</li>
<li>
<p><strong>「推荐文章十四」<a href="https://z.cash/technology/zksnarks/" target="_blank">zcash 官方科普文</a></strong></p>
<p>推荐值：❤️❤️❤️❤️</p>
<p>难度值：⭐️⭐️⭐️⭐️</p>
<p>这个系列的文章来自 zCash 官方博客。首先介绍了零知识的基本概念以及其应用到 zcash 中的大致思路。随后 7 篇文章分别对 7 个关键点进行了详细介绍（同态隐藏，多项式盲验证，KCA，完整的多项式盲验证，计算到多项式的转换，匹诺曹协议以及椭圆曲线配对），推荐给想深入了解 zk-SNARKs 实现原理的小伙伴。</p>
<p>原文链接：</p>
<ol>
<li>What are zk-SNARKs?：<a href="https://z.cash/technology/zksnarks/" target="_blank">https://z.cash/technology/zksnarks/</a></li>
<li>Explaining SNARKs Part I: Homomorphic Hidings <a href="https://electriccoin.co/blog/snark-explain/" target="_blank">https://electriccoin.co/blog/snark-explain/</a></li>
<li>Explaining SNARKs Part II: Blind Evaluation of Polynomials <a href="https://electriccoin.co/blog/snark-explain2/" target="_blank">https://electriccoin.co/blog/snark-explain2/</a></li>
<li>Explaining SNARKs Part III: The Knowledge of Coefficient Test and Assumption <a href="https://electriccoin.co/blog/snark-explain3/" target="_blank">https://electriccoin.co/blog/snark-explain3/</a></li>
<li>Explaining SNARKs Part IV: How to make Blind Evaluation of Polynomials Verifiable <a href="https://electriccoin.co/blog/snark-explain4/" target="_blank">https://electriccoin.co/blog/snark-explain4/</a></li>
<li>Explaining SNARKs Part V: From Computations to Polynomials <a href="https://electriccoin.co/blog/snark-explain5/" target="_blank">https://electriccoin.co/blog/snark-explain5/</a></li>
<li>Explaining SNARKs Part VI: The Pinocchio Protocol <a href="https://electriccoin.co/blog/snark-explain6/" target="_blank">https://electriccoin.co/blog/snark-explain6/</a></li>
<li>Explaining SNARKs Part VII: Pairings of Elliptic Curves](<a href="https://electriccoin.co/blog/snark-explain7/" target="_blank">https://electriccoin.co/blog/snark-explain7/</a></li>
<li>中文翻译版本链接：<a href="https://www.jianshu.com/p/b6a14c472cc1%E3%80%81https://www.jianshu.com/p/92f54fc08d58" target="_blank">https://www.jianshu.com/p/b6a14c472cc1、https://www.jianshu.com/p/92f54fc08d58</a></li>
</ol>
</li>
<li>
<p><strong>「推荐文章十五」<a href="https://arxiv.org/pdf/1906.07221.pdf" target="_blank">Why and How zk-SNARK Works</a></strong></p>
<p>推荐值：❤️❤️❤️❤️❤️</p>
<p>难度值：⭐️⭐️⭐️</p>
<p>作者将其学习 zk-SNARK 的经验总结成了一份 PDF 文档并分成 8 篇文章发布到了 Medium 上。与大部分的 zk-SNARK 科普文不同，这个系列的文章没有直接开始讲 zk-SNARK，而是从最基本的数学原理讲起，讲解得非常细致，特别适合数学和密码学基础相对薄弱的小伙伴。</p>
<p>原文链接：</p>
<ol>
<li>PDF 完整版：<a href="https://arxiv.org/pdf/1906.07221.pdf" target="_blank">https://arxiv.org/pdf/1906.07221.pdf</a></li>
<li>Why and How zk-SNARK Works 1: Introduction &amp; the Medium of a Proof：<a href="https://medium.com/@imolfar/why-and-how-zk-snark-works-1-introduction-the-medium-of-a-proof-d946e931160" target="_blank">https://medium.com/@imolfar/why-and-how-zk-snark-works-1-introduction-the-medium-of-a-proof-d946e931160</a></li>
<li>Why and How zk-SNARK Works 2: Proving Knowledge of a Polynomial：<a href="https://medium.com/@imolfar/why-and-how-zk-snark-works-2-proving-knowledge-of-a-polynomial-f817760e2805" target="_blank">https://medium.com/@imolfar/why-and-how-zk-snark-works-2-proving-knowledge-of-a-polynomial-f817760e2805</a></li>
<li>Why and How zk-SNARK Works 3: Non-interactivity &amp; Distributed Setup：<a href="https://medium.com/@imolfar/why-and-how-zk-snark-works-3-non-interactivity-distributed-setup-c0310c0e5d1c" target="_blank">https://medium.com/@imolfar/why-and-how-zk-snark-works-3-non-interactivity-distributed-setup-c0310c0e5d1c</a></li>
<li>Why and How zk-SNARK Works 4: General-Purpose Computation：<a href="https://medium.com/@imolfar/why-and-how-zk-snark-works-4-general-purpose-computation-dcdc8081ee42" target="_blank">https://medium.com/@imolfar/why-and-how-zk-snark-works-4-general-purpose-computation-dcdc8081ee42</a></li>
<li>Why and How zk-SNARK Works 5: Variable Polynomials：<a href="https://medium.com/@imolfar/why-and-how-zk-snark-works-5-variable-polynomials-3b4e06859e30" target="_blank">https://medium.com/@imolfar/why-and-how-zk-snark-works-5-variable-polynomials-3b4e06859e30</a></li>
<li>Why and How zk-SNARK Works 6: Verifiable Computation Protocol：<a href="https://medium.com/@imolfar/why-and-how-zk-snark-works-6-verifiable-computation-protocol-1aa19f95a5cc" target="_blank">https://medium.com/@imolfar/why-and-how-zk-snark-works-6-verifiable-computation-protocol-1aa19f95a5cc</a></li>
<li>Why and How zk-SNARK Works 7: Constraints and Public Inputs：<a href="https://medium.com/@imolfar/why-and-how-zk-snark-works-7-constraints-and-public-inputs-e95f6596dd1c" target="_blank">https://medium.com/@imolfar/why-and-how-zk-snark-works-7-constraints-and-public-inputs-e95f6596dd1c</a></li>
<li>Why and How zk-SNARK Works 8: Zero-Knowledge Computation：<a href="https://medium.com/@imolfar/why-and-how-zk-snark-works-8-zero-knowledge-computation-f120339c2c55" target="_blank">https://medium.com/@imolfar/why-and-how-zk-snark-works-8-zero-knowledge-computation-f120339c2c55</a></li>
</ol>
</li>
<li>
<p><strong>「推荐文章十六」 <a href="https://blog.ethereum.org/2016/12/05/zksnarks-in-a-nutshell/" target="_blank">zkSNARKs in a nutshell</a></strong></p>
<p>推荐值：❤️❤️❤️</p>
<p>难度值：⭐️⭐️⭐️</p>
<p>这篇文章对零知识证明做了总结，分成四个部分：</p>
<ol>
<li>编码成一个多项式问题</li>
<li>简单随机抽样</li>
<li>同态（Homomorphic）编码 / 加密</li>
<li>零知识</li>
</ol>
<p>文章首先介绍了零知识证明，然后又讲解了zk-SNARKs 的实现，最后分析了将零知识证明结合到以太坊上的作用和方式。</p>
<p>原文链接：<a href="https://blog.ethereum.org/2016/12/05/zksnarks-in-a-nutshell/" target="_blank">https://blog.ethereum.org/2016/12/05/zksnarks-in-a-nutshell/</a></p>
<p>中文翻译版本：<a href="https://zhuanlan.zhihu.com/p/31780893" target="_blank">https://zhuanlan.zhihu.com/p/31780893</a></p>
</li>
<li>
<p><strong>「推荐文章十七」<a href="https://medium.com/@weijiek/how-i-learned-zk-snarks-from-scratch-177a01c5514e" target="_blank">Zero-knowledge proofs, a board game, and leaky abstractions: how I learned zk-SNARKs from scratch</a></strong></p>
<p>推荐值：❤️❤️❤️</p>
<p>难度值：⭐️⭐️⭐️</p>
<p>作者坚持一个观点：学习新技能的一个很好的方法是用它建立一些东西。这篇文章就是在介绍作者是如何通过实现一个小的应用来学习 zk-SNARKs 的。文章主要介绍了作者的实现过程和他的思考，文中有很多好的经验时候大家学习。</p>
<p>原文链接：<a href="https://medium.com/@weijiek/how-i-learned-zk-snarks-from-scratch-177a01c5514e" target="_blank">https://medium.com/@weijiek/how-i-learned-zk-snarks-from-scratch-177a01c5514e</a></p>
</li>
<li>
<p><strong>「推荐文章十八」<a href="https://mp.weixin.qq.com/s/eU8mp81VhpV-g1x89-uZYA" target="_blank">零知识证明 - 从QSP到QAP</a></strong></p>
<p>推荐值：❤️❤️❤️</p>
<p>难度值：⭐️⭐️⭐️</p>
<p>这篇文章主要介绍了 QSP/QAP ，QAP 和 QSP 问题类似。QAP 问题的zkSNARK 的证明验证过程和 QSP 非常相似。对这部分感兴趣的小伙伴推荐读一读。</p>
<p>原文链接：<a href="https://mp.weixin.qq.com/s/eU8mp81VhpV-g1x89-uZYA" target="_blank">https://mp.weixin.qq.com/s/eU8mp81VhpV-g1x89-uZYA</a></p>
</li>
</ul><p>“零知识证明技术就像一个江湖，而 zk-SNARKs 是只是比较著名的门派。而在这个江湖中，还有很多其他的门派，他们风格各异，使用的武器也不尽相同。“[3] zk-SNARKs 协议涉及的技术构件很多，也较为复杂，深入学习这部分确实需要下很多功夫。</p></section><section><h2>5. 零知识证明协议<a href="#5-零知识证明协议"><span>#</span></a></h2><p>零知识证明协议很多，每个协议的实现也各不相同，有些协议已经应用到了实际的领域，有些还在探索中。推荐几篇介绍不错的文章。</p><ul>
<li>
<p><strong>「推荐文章十九」STARKs 科普</strong></p>
<p>推荐值：❤️❤️❤️❤️</p>
<p>难度值：⭐️⭐️⭐️⭐️</p>
<p>V 神的这个科普系列文章，非常详细得介绍了 STARKs 的实现，分成三个部分进行讲解。</p>
<p>原文链接：</p>
<ol>
<li>
<p>STARKs, Part I: Proofs with Polynomials：<a href="https://vitalik.ca/general/2017/11/09/starks_part_1.html" target="_blank">https://vitalik.ca/general/2017/11/09/starks_part_1.html</a></p>
<p>中文翻译版本：<a href="https://ethfans.org/posts/starks_part_1" target="_blank">https://ethfans.org/posts/starks_part_1</a></p>
</li>
<li>
<p>STARKs, Part II: Thank Goodness It’s FRI-day：<a href="https://vitalik.ca/general/2017/11/22/starks_part_2.html" target="_blank">https://vitalik.ca/general/2017/11/22/starks_part_2.html</a></p>
<p>中文翻译版本：<a href="https://ethfans.org/posts/starks_part_2" target="_blank">https://ethfans.org/posts/starks_part_2</a></p>
</li>
<li>
<p>STARKs, Part 3: Into the Weeds：<a href="https://vitalik.ca/general/2018/07/21/starks_part_3.html" target="_blank">https://vitalik.ca/general/2018/07/21/starks_part_3.html</a>)</p>
<p>中文翻译版本：<a href="https://ethfans.org/posts/starks_part_3_1" target="_blank">https://ethfans.org/posts/starks_part_3_1</a></p>
<p>中文翻译版本：<a href="https://ethfans.org/posts/starks_part_3_2" target="_blank">https://ethfans.org/posts/starks_part_3_2</a></p>
</li>
</ol>
</li>
<li>
<p><strong>「推荐文章二十」 <a href="https://vitalik.ca/general/2019/09/22/plonk.html" target="_blank">Understanding PLONK</a></strong></p>
<p>推荐值：❤️❤️❤️❤️</p>
<p>难度值：⭐️⭐️⭐️⭐️</p>
<p>这篇文章同样来自 V 神的博客，介绍了 PLONK 的工作原理。PLONK 是一种全新的零知识证明系统，支持通用或可更新的可信设置（trusted setup），作者是 Filecoin 母公司 Protocol Labs 的研究员 Ariel Gabizon 和以太坊隐私交易协议 Aztec Protocol 的两名研究人员 Zachary J. Williamson、Oana Ciobotaru。</p>
<p>原文链接：<a href="https://vitalik.ca/general/2019/09/22/plonk.html" target="_blank">https://vitalik.ca/general/2019/09/22/plonk.html</a></p>
<p>中文翻译版本：<a href="https://www.8btc.com/article/486086" target="_blank">https://www.8btc.com/article/486086</a></p>
</li>
<li>
<p><strong>「推荐文章二十一」<a href="https://github.com/huyuguang/zkpblog/blob/master/groth09.md" target="_blank">Groth09 笔记</a></strong></p>
<p>推荐值：❤️❤️</p>
<p>难度值：⭐️⭐️⭐️</p>
<p>这篇文章作者huyuguang，文中对 Groth09 论文[4]的内容进行了总结，对大家学习 Groth09 有所帮助。</p>
<p>原文链接：<a href="https://github.com/huyuguang/zkpblog/blob/master/groth09.md" target="_blank">https://github.com/huyuguang/zkpblog/blob/master/groth09.md</a></p>
</li>
<li>
<p><strong>「推荐文章二十二」 零知识证明 - Groth16 算法介绍</strong></p>
<p>推荐值：❤️❤️</p>
<p>难度值：⭐️⭐️⭐️</p>
<p>Star Li 的这两篇文章主要从工程应用理解的角度介绍了 Groth16 算法的证明和验证过程，推荐给学习 Groth16 算法的小伙伴。</p>
<p>原文链接</p>
<ol>
<li>零知识证明 - Groth16 算法介绍：<a href="https://mp.weixin.qq.com/s/SguBb5vyAm2Vzht7WKgzug" target="_blank">https://mp.weixin.qq.com/s/SguBb5vyAm2Vzht7WKgzug</a></li>
<li>零知识证明 - 有关 Groth16 的zk证明的理解：<a href="https://mp.weixin.qq.com/s/x1ggw3VplXAIeL87D5bUfw" target="_blank">https://mp.weixin.qq.com/s/x1ggw3VplXAIeL87D5bUfw</a></li>
</ol>
</li>
</ul><p>对于零知识证明各个协议介绍的文章还比较有限，随着应用的增多，相信这方面的文章也会越来越多。</p></section><section><h2>6.  零知识证明在区块链领域的应用<a href="#6--零知识证明在区块链领域的应用"><span>#</span></a></h2><p>零知识证明技术是随着区块链的发展逐渐走入大众视野的，目前零知识证明结合区块链的研究和应用也越来越多。</p><ul>
<li>
<p><strong>「推荐文章二十三」 <a href="https://www.odaily.com/post/5133827" target="_blank">一文读懂区块链中的零知识证明</a></strong></p>
<p>推荐值：❤️❤️❤️</p>
<p>难度值：⭐️⭐️⭐️</p>
<p>这篇来自BFTF技术社区联盟的文章介绍了零知识证明在 zcash 和门罗币上的应用。</p>
<p>原文链接：<a href="https://www.odaily.com/post/5133827" target="_blank">https://www.odaily.com/post/5133827</a></p>
</li>
<li>
<p><strong>「推荐文章二十四」<a href="https://medium.com/hackernoon/how-to-prove-that-you-know-something-without-revealing-it-zero-knowledge-proofs-zcash-ethereum-43ce35d4d1c5" target="_blank">How to prove that you know something, without revealing it? Zero-knowledge proofs, ZCash, Ethereum.</a></strong></p>
<p>推荐值：❤️❤️❤️</p>
<p>难度值：⭐️⭐️</p>
<p>这篇文章介绍了零知识证明在 Zcash 和以太坊上的应用。</p>
<p>原文链接：<a href="https://medium.com/hackernoon/how-to-prove-that-you-know-something-without-revealing-it-zero-knowledge-proofs-zcash-ethereum-43ce35d4d1c5" target="_blank">https://medium.com/hackernoon/how-to-prove-that-you-know-something-without-revealing-it-zero-knowledge-proofs-zcash-ethereum-43ce35d4d1c5</a></p>
</li>
<li>
<p><strong>「推荐文章二十五」<a href="https://blog.keep.network/zero-knowledge-proofs-zcash-and-ethereum-f6d89fa7cba8" target="_blank">Zero-knowledge proofs, Zcash, and Ethereum</a></strong></p>
<p>推荐值：❤️❤️❤️</p>
<p>难度值：⭐️⭐️</p>
<p>这篇文章介绍了零知识证明在 Zcash 和以太坊上的应用。</p>
<p>原文链接：<a href="https://blog.keep.network/zero-knowledge-proofs-zcash-and-ethereum-f6d89fa7cba8" target="_blank">https://blog.keep.network/zero-knowledge-proofs-zcash-and-ethereum-f6d89fa7cba8</a></p>
</li>
<li>
<p><strong>「推荐文章二十六」<a href="https://mp.weixin.qq.com/s/9QccZtFcvGwne-NN4BBA5w" target="_blank">零知识证明 - zk-SNARK应用场景分析</a></strong></p>
<p>推荐值：❤️❤️❤️</p>
<p>难度值：⭐️⭐️</p>
<p>这篇文章介绍了零知识证明在 Zcash，Filecoin项目和 Loopring DEX 3.0 协议中的应用。</p>
<p>原文链接：<a href="https://mp.weixin.qq.com/s/9QccZtFcvGwne-NN4BBA5w" target="_blank">https://mp.weixin.qq.com/s/9QccZtFcvGwne-NN4BBA5w</a></p>
</li>
<li>
<p><strong>「推荐文章二十七」<a href="https://blog.cryptographyengineering.com/2013/04/11/zerocoin-making-bitcoin-anonymous/" target="_blank">Zerocoin: making Bitcoin anonymous</a></strong></p>
<p>推荐值：❤️❤️❤️❤️</p>
<p>难度值：⭐️⭐️⭐️</p>
<p>这篇文章介绍了 Zerocoin 协议是如何利用 zk-SNARKs 在区块链上实现匿名的。</p>
<p>原文链接：<a href="https://blog.cryptographyengineering.com/2013/04/11/zerocoin-making-bitcoin-anonymous/" target="_blank">https://blog.cryptographyengineering.com/2013/04/11/zerocoin-making-bitcoin-anonymous/</a></p>
</li>
<li>
<p><strong>「推荐文章二十八」<a href="https://zhuanlan.zhihu.com/p/24440530" target="_blank">不是程序员也能看懂的ZCash零知识证明</a></strong></p>
<p>推荐值：❤️❤️❤️</p>
<p>难度值：⭐️⭐️⭐️</p>
<p>这篇文章使用比较通俗易懂的语言介绍了 zCash 如何利用零知识证明实现匿名交易的。</p>
<p>原文链接：<a href="https://zhuanlan.zhihu.com/p/24440530" target="_blank">https://zhuanlan.zhihu.com/p/24440530</a></p>
</li>
<li>
<p><strong>「推荐文章二十九」 <a href="https://www.coindesk.com/monero-to-become-first-billion-dollar-crypto-to-implement-bulletproofs-tech" target="_blank">Monero to Become First Billion-Dollar Crypto to Implement ‘Bulletproofs’ Tech</a></strong></p>
<p>推荐值：❤️❤️❤️</p>
<p>难度值：⭐️⭐️⭐️</p>
<p>这篇文章介绍了 Monero 如何使用 Bulletproofs 技术实现隐私特性的。</p>
<p>原文链接：<a href="https://www.coindesk.com/monero-to-become-first-billion-dollar-crypto-to-implement-bulletproofs-tech" target="_blank">https://www.coindesk.com/monero-to-become-first-billion-dollar-crypto-to-implement-bulletproofs-tech</a></p>
</li>
<li>
<p><strong>「推荐文章三十」 <a href="https://mp.weixin.qq.com/s/TCYDfOAle0K3D69eBm6HNw" target="_blank">zkPoD&lt;区块链&gt;，零知识证明与形式化验证，实现无中介、零信任的公平交易</a></strong></p>
<p>推荐值：❤️❤️❤️❤️</p>
<p>难度值：⭐️⭐️⭐️</p>
<p>这是安比实验室今年发布的基于零知识证明的公平交易协议。</p>
<p>原文链接：<a href="https://mp.weixin.qq.com/s/TCYDfOAle0K3D69eBm6HNw" target="_blank">https://mp.weixin.qq.com/s/TCYDfOAle0K3D69eBm6HNw</a></p>
</li>
<li>
<p><strong>「推荐文章三十一」 <a href="https://mp.weixin.qq.com/s/oTbzyqtc-TzJXbMafj28DQ" target="_blank">零知识证明 - Loopring DEX 3.0</a></strong></p>
<p>推荐值：❤️❤️❤️</p>
<p>难度值：⭐️⭐️⭐️</p>
<p>这篇文章介绍了 Loopring DEX 3.0 协议的零知识证明部分实现原理。</p>
<p>原文链接：<a href="https://mp.weixin.qq.com/s/oTbzyqtc-TzJXbMafj28DQ" target="_blank">https://mp.weixin.qq.com/s/oTbzyqtc-TzJXbMafj28DQ</a></p>
</li>
</ul><p>零知识证明的应用正在逐步增加，从最早的公链 zCash，Monero，到最近基于以太坊平台的 zkPoD, Loopring DEX 3.0应用等，零知识证明在区块链领域的应用将越来越多。</p></section><section><h2>7. 零知识证明相关的技术和漏洞分析文章<a href="#7-零知识证明相关的技术和漏洞分析文章"><span>#</span></a></h2><p>零知识证明技术涉及的知识内容很多，在实际的应用场景中，零知识证明的实现还存在诸多的挑战，协议安全，性能等等问题都有可能限制其发展。这一节推荐一些技术分析和漏洞分析的文章。</p><ul>
<li>
<p><strong>「推荐文章三十二」 A Marlin is One of the Fastest SNARKs in the Ocean</strong></p>
<p>推荐值：❤️❤️❤️</p>
<p>难度值：⭐️⭐️⭐️⭐️</p>
<p>这篇文章来自于博客 <a href="https://www.benthamsgaze.org/" target="_blank">Bentham’s Gaze</a>，文章观点认为 Marlin 是最快的 SNARKs 方案，并将其与其它的方案进行了比较。</p>
<p>原文链接：<a href="https://www.benthamsgaze.org/2019/09/19/a-marlin-is-one-of-the-fastest-snarks-in-the-ocean/" target="_blank">https://www.benthamsgaze.org/2019/09/19/a-marlin-is-one-of-the-fastest-snarks-in-the-ocean/</a></p>
</li>
<li>
<p><strong>「推荐文章三十三」<a href="https://www.benthamsgaze.org/2016/10/25/how-to-do-zero-knowledge-from-discrete-logs-in-under-7kb/" target="_blank">How to do Zero-Knowledge from Discrete-Logs in under 7kB</a></strong></p>
<p>推荐值：❤️❤️❤️</p>
<p>难度值：⭐️⭐️⭐️</p>
<p>这篇文章同样来自于博客 <a href="https://www.benthamsgaze.org/" target="_blank">Bentham’s Gaze</a>，文章介绍了Groth09 论文中的优化方案。</p>
<p>原文链接：<a href="https://www.benthamsgaze.org/2016/10/25/how-to-do-zero-knowledge-from-discrete-logs-in-under-7kb/" target="_blank">https://www.benthamsgaze.org/2016/10/25/how-to-do-zero-knowledge-from-discrete-logs-in-under-7kb/</a></p>
</li>
<li>
<p><strong>「推荐文章三十四」<a href="https://sec-bit.github.io/blog/page/2/" target="_blank">zkSNARK 合约「输入假名」漏洞致众多混币项目爆雷</a></strong></p>
<p>推荐值：❤️❤️❤️</p>
<p>难度值：⭐️⭐️⭐️</p>
<p>这篇文章的作者是安比实验室 p0n1，文章介绍了大量零知识证明项目由于错误地使用了某个 zkSNARKs 合约库，引入「输入假名 (Input Aliasing) 」漏洞，可导致伪造证明、双花、重放等攻击行为发生，且攻击成本极低。</p>
<p>原文链接：<a href="https://sec-bit.github.io/blog/page/2/" target="_blank">https://sec-bit.github.io/blog/page/2/</a></p>
</li>
<li>
<p><strong>「推荐文章三十五」<a href="https://zhuanlan.zhihu.com/p/87690026" target="_blank">硬核！360高级安全专家彭峙酿以Zcash为例，谈零知识性证明的安全和隐私问题</a></strong></p>
<p>推荐值：❤️❤️❤️❤️</p>
<p>难度值：⭐️⭐️⭐️</p>
<p>这篇文章是对360高级安全专家彭峙酿博士在 CCF 会议上分享报告的整理。报告中介绍了比特币的隐私问题，零知识证明技术，zk-SNARKs，以及多个实现漏洞。报告干货满满。</p>
<p>原文链接：<a href="https://zhuanlan.zhihu.com/p/87690026" target="_blank">https://zhuanlan.zhihu.com/p/87690026</a></p>
</li>
<li>
<p><strong>「推荐文章三十六」<a href="https://github.com/huyuguang/zkpblog/blob/master/%E6%9C%89%E9%99%90%E5%9F%9F.md" target="_blank">零知识证明中所涉及的有限域</a></strong></p>
<p>推荐值：❤️❤️❤️</p>
<p>难度值：⭐️⭐️⭐️⭐️</p>
<p>有限域的计算是实现零知识证明协议的一个非常重要的环境，这篇文章对零知识证明中所涉及导的有限域的知识进行了介绍，非常有用。</p>
<p>原文链接：<a href="https://github.com/huyuguang/zkpblog/blob/master/%E6%9C%89%E9%99%90%E5%9F%9F.md" target="_blank">https://github.com/huyuguang/zkpblog/blob/master/有限域.md</a></p>
</li>
</ul></section><section><h2>8. 零知识证明开源仓库及介绍<a href="#8-零知识证明开源仓库及介绍"><span>#</span></a></h2><p>下面介绍几个热度比较高的零知识证明实现仓库及其源码分析文章，很多的零知识项目都是基于这几个仓库的代码做的。</p><ul>
<li>
<p><a href="https://github.com/scipr-lab/libsnark" target="_blank">libsnark</a></p>
<p>libsnark 是实现一个 C++ 版本的零知识证明库。</p>
<p>仓库链接：<a href="https://github.com/scipr-lab/libsnark" target="_blank">https://github.com/scipr-lab/libsnark</a></p>
<ul>
<li>
<p><strong>「推荐文章三十七」<a href="https://mp.weixin.qq.com/s/UHqpfl6ImVwa4HtsiksqJA" target="_blank">零知识证明 - libsnark源代码分析</a></strong></p>
<p>推荐值：❤️❤️❤️</p>
<p>难度值：⭐️⭐️⭐️</p>
<p>原文链接：<a href="https://mp.weixin.qq.com/s/UHqpfl6ImVwa4HtsiksqJA" target="_blank">https://mp.weixin.qq.com/s/UHqpfl6ImVwa4HtsiksqJA</a></p>
</li>
<li>
<p><strong>「推荐文章三十八」<a href="https://zhuanlan.zhihu.com/p/46477111" target="_blank">零知识证明实战：libsnark</a></strong></p>
<p>推荐值：❤️❤️❤️</p>
<p>难度值：⭐️⭐️⭐️</p>
<p>原文链接：<a href="https://zhuanlan.zhihu.com/p/46477111" target="_blank">https://zhuanlan.zhihu.com/p/46477111</a></p>
</li>
</ul>
</li>
<li>
<p><a href="https://github.com/zkcrypto/bellman" target="_blank">bellman</a></p>
<p>bellman是Zcash团队用Rust语言开发的一个zk-SNARK软件库，实现了Groth16 算法。</p>
<p>仓库链接：<a href="https://github.com/zkcrypto/bellman" target="_blank">https://github.com/zkcrypto/bellman</a></p>
<ul>
<li>
<p><strong>「推荐文章三十九」<a href="https://mp.weixin.qq.com/s/NvX11tNSEpV1DR-3PwpIAQ" target="_blank">零知识证明 - bellman源码分析</a></strong></p>
<p>推荐值：❤️❤️</p>
<p>难度值：⭐️⭐️⭐️</p>
<p>原文链接：<a href="https://github.com/zcash/librustzcash/tree/master/bellman" target="_blank">https://github.com/zcash/librustzcash/tree/master/bellman</a></p>
</li>
</ul>
</li>
<li>
<p><a href="https://github.com/iden3/snarkjs" target="_blank">snarkjs</a></p>
<p>libsnark 是实现一个 javascript 版本的零知识证明库，实现了 Groth16。</p>
<p>仓库链接：<a href="https://github.com/iden3/snarkjs" target="_blank">https://github.com/iden3/snarkjs</a></p>
</li>
</ul></section><section><h2>9. 零知识证明相关论文<a href="#9-零知识证明相关论文"><span>#</span></a></h2><p>下面介绍一下零知识证明相关的学术论文，深入学习零知识证明研究成果的小伙伴可以去阅读以下的这些论文。</p><p>推荐值：❤️❤️❤️</p><p>难度值：⭐️⭐️⭐️⭐️⭐️</p><ul>
<li>
<p>1985 年，零知识证明技术首次被提出</p>
<p>原文链接：<a href="https://epubs.siam.org/doi/10.1137/0218012" target="_blank">The Knowledge Complexity of Interactive Proof Systems</a></p>
</li>
<li>
<p>BulletProof</p>
<ol>
<li>
<p><a href="https://link.springer.com/chapter/10.1007/978-3-642-03356-8_12" target="_blank">Gro09 </a>提出了一种证明“向量内积”的方法：</p>
<p>原文链接：<a href="https://link.springer.com/chapter/10.1007/978-3-642-03356-8_12" target="_blank">https://link.springer.com/chapter/10.1007/978-3-642-03356-8_12</a></p>
</li>
<li>
<p><a href="https://eprint.iacr.org/2016/263" target="_blank">BCC+16</a> 找到了一种将算数电路编码为向量的方法，从而把电路可满足性的证明转化为向量内积的证明：</p>
<p>原文链接：<a href="https://eprint.iacr.org/2016/263" target="_blank">https://eprint.iacr.org/2016/263</a></p>
</li>
<li>
<p><a href="https://eprint.iacr.org/2017/1066" target="_blank">BulletProof</a>继续改进了这种方案：</p>
<p>原文链接：<a href="https://eprint.iacr.org/2017/1066" target="_blank">https://eprint.iacr.org/2017/1066</a></p>
</li>
</ol>
</li>
<li>
<p>zkSNARKs with trusted setup</p>
<ol>
<li>
<p><a href="https://link.springer.com/chapter/10.1007/978-3-642-17373-8_19" target="_blank">Groth10</a> 引入了preprocessing的步骤，通过可信第三方生成Common Reference String来实现无交互证明:</p>
<p>原文链接：<a href="https://link.springer.com/chapter/10.1007/978-3-642-17373-8_19" target="_blank">https://link.springer.com/chapter/10.1007/978-3-642-17373-8_19</a></p>
</li>
<li>
<p><a href="https://eprint.iacr.org/2012/215" target="_blank">GGPR13</a> 引入了另一种算数电路编码方式，即Quadratic Arithmetic Program(QAP)，大大提升了证明的效率:</p>
<p>原文链接：<a href="https://eprint.iacr.org/2012/215" target="_blank">https://eprint.iacr.org/2012/215</a></p>
</li>
<li>
<p><a href="https://eprint.iacr.org/2013/279" target="_blank">Pinocchio</a> 和 <a href="https://eprint.iacr.org/2016/260" target="_blank">Groth16</a> 等是在此基础上的改进:</p>
<p>原文链接：<a href="https://eprint.iacr.org/2013/279" target="_blank">https://eprint.iacr.org/2013/279</a></p>
<p>原文链接：<a href="https://eprint.iacr.org/2016/260" target="_blank">https://eprint.iacr.org/2016/260</a></p>
</li>
</ol>
</li>
<li>
<p><a href="https://acmccs.github.io/papers/p2087-amesA.pdf" target="_blank">Ligero: Lightweight Sublinear Arguments Without a Trusted Setup</a>:</p>
<p>原文链接：<a href="https://acmccs.github.io/papers/p2087-amesA.pdf" target="_blank">https://acmccs.github.io/papers/p2087-amesA.pdf</a></p>
</li>
<li>
<p>PLONK:</p>
<p>原文链接：<a href="https://eprint.iacr.org/2019/953" target="_blank">https://eprint.iacr.org/2019/953</a></p>
</li>
<li>
<p>Marlin</p>
<p>原文链接：<a href="https://eprint.iacr.org/2019/1047.pdf" target="_blank">https://eprint.iacr.org/2019/1047.pdf</a></p>
</li>
<li>
<p>Sonic</p>
<p>原文链接：<a href="https://eprint.iacr.org/2019/099" target="_blank">https://eprint.iacr.org/2019/099</a></p>
</li>
<li>
<p>Libra</p>
<p>原文链接：<a href="https://eprint.iacr.org/2019/317" target="_blank">https://eprint.iacr.org/2019/317</a></p>
</li>
<li>
<p>Hyrax</p>
<p>原文链接：<a href="https://eprint.iacr.org/2017/1132.pdf" target="_blank">https://eprint.iacr.org/2017/1132.pdf</a></p>
</li>
<li>
<p>zk-STARKs</p>
<p>原文链接：<a href="https://eprint.iacr.org/2018/046" target="_blank">https://eprint.iacr.org/2018/046</a></p>
</li>
</ul></section><section><h2>10. 零知识证明学习资料推荐网站<a href="#10-零知识证明学习资料推荐网站"><span>#</span></a></h2><ul>
<li>
<p><a href="https://github.com/matter-labs/awesome-zero-knowledge-proofs" target="_blank">awesome-zero-knowledge-proofs</a></p>
<p>推荐值：❤️❤️❤️❤️</p>
<p>这是一个 Github 仓库，收录了一系列零知识证明的学习资料</p>
<p>链接：<a href="https://github.com/matter-labs/awesome-zero-knowledge-proofs" target="_blank">https://github.com/matter-labs/awesome-zero-knowledge-proofs</a></p>
</li>
<li>
<p><a href="https://zkp.science/" target="_blank">Zero-Knowledge Proofs</a></p>
<p>推荐值：❤️❤️❤️❤️</p>
<p>这个网站也收录了一系列零知识证明的学习资料，相对来说学术性更强一些。</p>
<p>链接：<a href="https://zkp.science/" target="_blank">https://zkp.science</a></p>
</li>
<li>
<p><a href="https://zkproof.org/workshop2/abstracts.html" target="_blank">zkproof</a></p>
<p>推荐值：❤️❤️❤️❤️</p>
<p>ZKProof.org 是为规范零知识证明的使用而形成的一个组织，它的网站上有大量关于零知识证明的资料。</p>
<p>链接：<a href="https://zkproof.org/" target="_blank">https://zkproof.org/</a></p>
</li>
<li>
<p><a href="https://www.benthamsgaze.org" target="_blank">benthamsgaze.org</a></p>
<p>推荐值：❤️❤️❤️</p>
<p>这是一个来自UCL信息安全研究人员组成的团队的博客，它的博客上会经常发布一些零知识证明的文章。</p>
<p>链接：<a href="https://www.benthamsgaze.org" target="_blank">https://www.benthamsgaze.org</a></p>
</li>
</ul></section><section><h2>参考文献<a href="#参考文献"><span>#</span></a></h2><p>[1] <a href="https://isi.jhu.edu/~mgreen/" target="_blank">https://isi.jhu.edu/~mgreen/</a></p><p>[2] <a href="https://www.benthamsgaze.org/about" target="_blank">https://www.benthamsgaze.org/about</a></p><p>[3] <a href="https://zhuanlan.zhihu.com/p/31651393" target="_blank">https://zhuanlan.zhihu.com/p/31651393</a></p><p>[4] <a href="https://link.springer.com/chapter/10.1007/978-3-642-03356-8_12" target="_blank">https://link.springer.com/chapter/10.1007/978-3-642-03356-8_12</a></p><p>[5] <a href="https://github.com/matter-labs/awesome-zero-knowledge-proofs" target="_blank">https://github.com/matter-labs/awesome-zero-knowledge-proofs</a></p><p>[6] <a href="https://zkp.science/" target="_blank">https://zkp.science/</a></p><p>[7] <a href="https://zhuanlan.zhihu.com/p/89386868?utm_source=wechat_session&amp;utm_medium=social&amp;utm_oi=26765481213952" target="_blank">https://zhuanlan.zhihu.com/p/89386868?utm_source=wechat_session&amp;utm_medium=social&amp;utm_oi=26765481213952</a></p></section></section>]]></content>
    </entry>
</feed>
