Open WebUI with Qwen QwQ on Tavily Search

Open WebUI虽然内置了 Web Search 功能,而且能够调用 Tavily,但是效果比较一般。在社区提供的 Tool 中,victor1203提供了一个可用的 Tavily 搜索工具,Web Search with Tavily. 不过现时 Tavily 的 API 有所变动,所以需要进行一些修改。

我的 Open WebUI 是使用 docker 部署的,为了能够使用 tavily-python,需要进入 docker 容器并安装相应的依赖。步骤:

  1. 进入 docker 容器:docker exec -it openwebui bash
  2. 安装依赖:d open_webui && pip install tavily-python
  3. 退出 docker 容器:exit
  4. 重启 docker 容器:docker restart openwebui

然后打开“工作空间”,点击 “工具”,新建一个工具,填入以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
"""
title: Tavily Search Tool
author: victor1203
maintainer: sievelau
description: This tool performs internet searches using the Tavily API to get real-time information with advanced context and Q&A capabilities
required_open_webui_version: 0.4.0
requirements: tavily-python
version: 1.1.0
licence: MIT
"""

from pydantic import BaseModel, Field
from typing import Optional, Literal
from tavily import TavilyClient


class Tools:
def __init__(self):
"""Initialize the Tool with valves."""
self.valves = self.Valves()
self._client = None

class Valves(BaseModel):
tavily_api_key: str = Field(
"", description="Your Tavily API key (starts with 'tvly-')"
)
search_depth: str = Field(
"basic", description="Search depth - basic or advanced"
)
include_answer: bool = Field(
True, description="Include an AI-generated answer in the response"
)
max_results: int = Field(
5, description="Maximum number of search results to return (1-10)"
)

def _get_client(self) -> TavilyClient:
"""Get or create Tavily client instance."""
if self._client is None:
self._client = TavilyClient(api_key=self.valves.tavily_api_key)
return self._client

async def search(self, query: str, __event_emitter__=None) -> str:
"""
Perform a Tavily search and return the results.

Args:
query: The search query string
search_type: Type of search to perform:
- regular: Standard search with full results
- context: Optimized for RAG applications
- qa: Quick answer to a specific question

Returns:
A formatted string containing the search results
"""
try:
# Input validation
if not self.valves.tavily_api_key:
return "Error: Tavily API key not configured. Please set up the API key in the tool settings."

# Emit status that search is starting
if __event_emitter__:
await __event_emitter__(
{
"type": "status",
"data": {
"description": f"Initiating Tavily search...",
"done": False,
},
}
)

client = self._get_client()

# Perform the search based on type
if __event_emitter__:
await __event_emitter__(
{
"type": "status",
"data": {
"description": "Fetching search results...",
"done": False,
},
}
)
result = client.search(
query=query,
search_depth=self.valves.search_depth,
include_answer=self.valves.include_answer,
max_results=self.valves.max_results,
)

# Format regular search results
formatted_results = ""

# Include AI answer if available
if self.valves.include_answer and "answer" in result:
formatted_results += f"AI Answer:\n{result['answer']}\n\n"

# Add search results
formatted_results += "Tavily Search Results:\n\n"
for i, item in enumerate(
result.get("results", [])[: self.valves.max_results], 1
):
formatted_results += f"{i}. {item.get('title', 'No title')}\n"
formatted_results += f"URL: {item.get('url', 'No URL')}\n"
formatted_results += f"Content: {item.get('content', 'No content')}\n"
formatted_results += f"Relevance Score: {item.get('score', '0.0')}\n"
formatted_results += "\n"

if __event_emitter__:
await __event_emitter__(
{
"type": "status",
"data": {
"description": "Search completed successfully",
"done": True,
},
}
)

return formatted_results

except Exception as e:
error_message = f"An error occurred while performing the search: {str(e)}"
if __event_emitter__:
await __event_emitter__(
{
"type": "status",
"data": {"description": error_message, "done": True},
}
)
return error_message

具体的修改是:

  1. 删除了 search type,因为现在只有 search,具体的类型通过 topic 来指定,但是作为 genereal search,我们不需要这个参数。
  2. 把 formatted_results 的引导内容添加上 Tavily 这个关键词,方便 RAG Template 中指导模型
  3. 删除了 snippet,转而增加了 content 来指定搜索结果的内容
  4. 增加了 score,方便后面更改 RAG Template 来告诉模型根据此字段来判断相关度

之后,在工作空间的“模型”中新建一个 WebSearch,工具勾上我们刚才新增的这个tool,这就做好了一个 agent。

但是我们还需要更改 RAG Template。默认的 template 是设计成与内置的 web search 进行配合的,但是我们用 tool 来传递搜索结果会变成只有一个 source id,导致引用不正确。所以需要在 guideline 增加两条:

  1. - If the context contains "Tavily Search Results", you should not include inline citation. You should indicate the source url in this syntax: [Title](URL). Make sure you are using markdown link syntax and take care of the quotes. This rule take precedence over the previous rule about source_id.
  2. - If the context contains "Tavily Search Results", you should determine result quality by the Relevance Score.

Read More

NFS on ZFS and MacOS

之前通过 SMB 共享的一个目录,今天在写入 3G 左右大小的文件的时候,复制结束之后报错 -51。由于搜索了一番仍未找到解决方案,只能退而求其次试用 NFS 共享。根据 Archwiki 上关于 NFSZFS 的配置,结合今天的试错经历,总结如下:

配置固定端口方便 firewall 放行

需要配置 rpc.mountd 和 lockd 的端口,这两个的配置方式有所不同。

对于 rpc.mountd,只需要修改 /etc/nfs.conf,找到 [mountd] 这一节,去掉 port=0 这一行的注释并修改成自己喜欢的端口比如 2050 即可,然后 restart nfs-server.service 即可。

对于 lockd,亦即 rpcinfo -p 中输出的 nlockmgr 条目,则需要新建 /etc/modprobe.d/nfs.conf,填入以下内容来设置端口,比如改成 2051:

1
options lockd nlm_udpport=2051 nlm_tcpport=2051

保存之后需要 reboot 才能生效。

综合以上配置,加上 nfs 自身使用的 111 端口和 nfs v4 的 2049 端口,nftables需要以下规则放行相关端口:

1
2
tcp dport {2049,2050,2051,111} accept comment "nfs"
udp dport {2050,2051,111} accept comment "nfs"

如果不放行 mountd 的端口,那么在 Finder 连接 NFS 时会直接显示无法连接;如果不放行 lockd 的端口,那么 Finder 能够正常连接,但是打开子目录的时候就会超时并且断开连接。

Read More

Finance::Quote on MacOS and Windows

MacOS 上的 Finance::Quote 由于 Date::Simple 的 test 不通过会导致无法正常安装:

1
2
3
t/date.t ...... 1/227
# Failed test at t/date.t line 106.
# Looks like you failed 1 test of 227.

然而强制安装是可以正常运行的,所以使用以下命令就能强制安装了:

1
sudo env ARCHFLAGS='-arch arm64 -arch arm64e -arch x86_64' cpan -f -i Finance::Quote

Windows 稍有不同,首先安装 Strawberry Perl,然后打开 cmd,用 cpan -T 来跳过测试、强行安装需要的包。“需要的包”可以查看 gnucash 安装目录的 gnc-fq-update 文件,目前包括:

  • Test2
  • Finance::Quote
  • JSON::Parse

Read More

AFM Chapter 16

Announcement: All pictures are from the book Jonathan Berk, Peter DeMarzo - Corporate Finance (2024).

MM理论讨论了 tax shield 之后,变成了

给出的 optimal capital structure 就是 interest 等于 earning before tax,这样就可以完全不交税、实现 investor 收益最大化。然而现实世界里面的公司不会这样做,甚至负债水平远低于 optimal leverage。这一章讨论的就是还有什么其他的因素影响了公司选择 capital structure。


在 perfect capital market 下,if a firm:

  1. has access to capital markets
  2. can issue new securities at a fair price

那么只要 assets exceeds liabilities 就不会 default。


Decline in value is not caused by bankruptcy. 项目成败决定是否会出现 economic distress;leverage 影响是否会有 financial distress.

Read More

OpenGFW Experience

因为现在很多 App 会内置 resolver,只会跟系统的 DNS 请求一个它的 HTTPDNS 服务器的地址,后续就不会请求系统的 DNS 了,导致 DNS 广告过滤会失效。不想24小时开着 Surge,但是又想制裁广告的话,就只能用类似 SNI 阻断的方式了。

很久之前就听闻过 OpenGFW 的大名,这次终于找到了使用它的场景。恰好 AUR 里面有人已经做好了 PKGBUILD,直接一键安装就行。但是 service 文件得自己写,简单写一个:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[Unit]
Description=OpenGFW
After=network.target nftables.service

[Service]
Type=simple
WorkingDirectory=/run/OpenGFW
RuntimeDirectory=OpenGFW
ExecStart=/usr/bin/OpenGFW -c /etc/OpenGFW/config.yaml /etc/OpenGFW/rules.yaml
ExecReload=/bin/kill -HUP $MAINPID
TimeoutStopSec=5s
PrivateTmp=true
ProtectSystem=strict
NoNewPrivileges=true
Restart=always
RestartSec=5s
[Install]
WantedBy=multi-user.target

最简单的屏蔽就是使用 geosite:category-ads-all 来确定哪些是广告域名了。我们针对 HTTP、TLS 以及最潮最 IN 的 QUIC 进行判断,可以写以下规则到 rules.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
- name: block tls ad geosite
action: block
log: true
expr: geosite(string(tls?.req?.sni), "category-ads-all")

- name: block http ad geosite
action: block
log: true
expr: geosite(string(http?.req?.headers?.host), "category-ads-all")

- name: block quic ad geosite
action: block
log: true
expr: geosite(string(quic?.req?.sni), "category-ads-all")

如此一来就可以 start/enable 了。试过确实好,不过不建议对路由器本机生效,也就是 config.yamllocal 要设置成 false我对 0.4.1 版本进行的 speedtest 测试发现,对本机生效的话,延迟从 1 ms 提高到了 11 ms,下载和上传从 900 Mbps 分别降低到了 800 Mbps 和 600 Mbps,损失很大。

Read More

Quick Walk in Sai Kung

西贡去一趟不容易,从家里接驳去西贡专线的小巴不好坐,下次还是坐观塘线过去彩虹再转车比较好。

不得不说这个地方的海真的漂亮,不加滤镜就能很好看。40块港币来回船票,在香港算是便宜了。

我们今天只爬到黑山顶就折返了,主要是赶着回家看书。下次比较空闲的时候再来一趟,从厦门湾爬到桥咀,走个全程。

塑料虾诱饵

黑山顶

可爱暴龙

蓝海

Read More

credit agricole group CIB宣讲会

今天参加了 credit agricole group 的 corporate and investment bank 的宣讲会,跟几个 representative 聊了一下,特别是跟 structuring 的 Edwin Fung 聊了比较多。这几位都很友善,特别是负责 trading 业务的 Patrick 可以说是幽默风趣又非常有实力,加上运气很好。

整个宣讲会的过程都在强调 interest、passion;和 Edwin 聊了之后,strength 也成了我的困惑的问题。他说 strength 是假设现在和其他人竞争,你绝对不会输的一个方面。可是我就是那种两个强项(金融和编程)都会但是都不突出的人,这就很困难了。

Read More

InvPortAna Tutorial 1

今天上的课是 Investment and Portfolio Analysis 的 Tutorial 课程,主要介绍的是用 Excel 处理从雅虎等网站下载下来的股票数据,计算收益率、收益率的平均值、标准差、方差,以及如何画出正态分布的图的过程。另外还初步介绍了 Excel 的分析师工具,主要演示了怎么快速地把数据塞进自定义的 bin 里面做 frequency 直方图。

Read More

财经资讯

美国总统候选人的影响。特朗普当选:

  1. 美元强势
  2. 国债收益率高
    1. 特朗普倾向于减轻对金融机构的限制(特别是Fed最近想做的提高银行的capital 9%,特朗普上任的话这个政策几乎不会执行)
    2. 特朗普的贸易政策更加封闭
    3. 以上两点会导致美国的通胀压力更大,美联储的利率会更加谨慎

哈里斯当选:

  1. 美元和国债的波动不明朗
  2. 哈里斯对美联储造成的挑战会比特朗普少

降息其实有两方面影响:公司的负债压力降低(很直觉);但是公司的利息收入也会降低(导致Net interest income减少)。

Read More