langfuse的坑
1.langfuse不支持分片模式的ck集群
2.langfuse默认的CLICKHOUSE_CLUSTER_NAME是default,默认的CLICKHOUSE_DB也是default
如果使用了自定义的集群名和自定义的数据库,需要手动执行up.sh脚本
2.1修改环境变量LANGFUSE_AUTO_CLICKHOUSE_MIGRATION_DISABLED为true
2.2去sql的文件夹做批量替换
cd ./packages/shared/clickhouse/migrations/clustered/ sed -i 's/ON CLUSTER default/ON CLUSTER my_langfuse_cluster/g' *.sql find . -name "*.sql" -type f -exec sed -i 's/DROP TABLE IF EXISTS \([a-zA-Z0-9_]*\) ON CLUSTER/DROP TABLE IF EXISTS my_langfuse.\1 ON CLUSTER/g' {} \; find . -name "*.sql" -type f -exec sed -i 's/DROP VIEW IF EXISTS \([a-zA-Z0-9_]*\) ON CLUSTER/DROP VIEW IF EXISTS my_langfuse.\1 ON CLUSTER/g' {} \; cd /app/packages/shared sh ./clickhouse/scripts/up.sh
#注意如果ck的密码有特殊字符需要转义
3.使用的版本是3.175,有些表是表引擎不是可复制的表引擎,比如event_log表
后面的4.0版本就没有了这个event_log表
4.如果要使用多分片的集群
ck配置,新增一个1分片多副本的集群,新增一个用户
<my_langfuse_cluster>
<shard>
<internal_replication>true</internal_replication>
<replica>
<default_database>my_langfuse</default_database>
<host>my-clickhouse-1-0.my-clickhouse-1-headless</host>
<port>9000</port>
<user>langfuse</user>
<password>123456</password>
</replica>
<replica>
<default_database>my_langfuse</default_database>
<host>my-clickhouse-2-0.my-clickhouse-2-headless</host>
<port>9000</port>
<user>langfuse</user>
<password>123456</password>
</replica>
<replica>
<default_database>my_langfuse</default_database>
<host>my-clickhouse-3-0.my-clickhouse-3-headless</host>
<port>9000</port>
<user>langfuse</user>
<password>123456</password>
</replica>
<replica>
<default_database>my_langfuse</default_database>
<host>my-clickhouse-4-0.my-clickhouse-4-headless</host>
<port>9000</port>
<user>langfuse</user>
<password>123456</password>
</replica>
<replica>
<default_database>my_langfuse</default_database>
<host>my-clickhouse-5-0.my-clickhouse-5-headless</host>
<port>9000</port>
<user>langfuse</user>
<password>123456</password>
</replica>
<replica>
<default_database>my_langfuse</default_database>
<host>my-clickhouse-6-0.my-clickhouse-6-headless</host>
<port>9000</port>
<user>langfuse</user>
<password>123456</password>
</replica>
</shard>
</my_langfuse_cluster>
<langfuse>
<password>123456</password>
<networks>
<ip>::/0</ip>
</networks>
<!-- Settings profile for user. -->
<profile>default</profile>
<!-- Quota for user. -->
<quota>default</quota>
<!-- User can create other users and grant rights to them. -->
<access_management>1</access_management>
</langfuse>
修改langfuse的建表语句
sed -i "s#ReplicatedReplacingMergeTree(event_ts, is_deleted)#ReplicatedReplacingMergeTree('/clickhouse/tables/1/{database}/{table}', '{replica}', event_ts, is_deleted)#g" *.sql sed -i "s#ReplicatedAggregatingMergeTree#ReplicatedAggregatingMergeTree('/clickhouse/tables/1/{database}/{table}', '{replica}')#g" 0009_add_project_environments.up.sql
5.测试脚本
from openai import OpenAI from langfuse import Langfuse, propagate_attributes, get_client import tiktoken import sys # ====================== 硬编码配置 ====================== LANGFUSE_PUBLIC_KEY = "pk-lf-xxxxxxxxxx" LANGFUSE_SECRET_KEY = "sk-lf-xxxxxxxxxxxxx" LANGFUSE_HOST = "http://langfuse-web.www:20011" LLM_API_KEY = "xxxxxxxxxx" LLM_BASE_URL = "https://open.bigmodel.cn/api/coding/paas/v4" LLM_MODEL = "glm-5.3-flash" # ======================================================== # 初始化Langfuse langfuse = Langfuse( public_key=LANGFUSE_PUBLIC_KEY, secret_key=LANGFUSE_SECRET_KEY, host=LANGFUSE_HOST, debug=True ) lf_client = get_client() # 初始化LLM客户端 llm_client = OpenAI( api_key=LLM_API_KEY, base_url=LLM_BASE_URL ) # Token估算工具(兜底用) def count_message_tokens(messages): enc = tiktoken.get_encoding("cl100k_base") total = 0 for msg in messages: total += len(enc.encode(msg["content"])) return total def count_text_tokens(text: str): enc = tiktoken.get_encoding("cl100k_base") return len(enc.encode(text)) def safe_input(prompt: str) -> str: """ 替代原生input(),处理乱码/特殊字节,避免UnicodeDecodeError崩溃 """ sys.stdout.write(prompt) sys.stdout.flush() raw_bytes = sys.stdin.buffer.readline() if not raw_bytes: raise EOFError() # 解码容错,遇到非法字节直接替换,不抛异常 text = raw_bytes.decode("utf‑8", errors="replace") # 去掉末尾换行 return text.rstrip("\r\n") def chat_round(messages): """ 传入完整messages上下文,执行一轮对话,完整埋点上报 :param messages: list[dict] 完整对话上下文 :return: answer, (prompt_tok, comp_tok, total_tok) """ user_query = messages[-1]["content"] with lf_client.start_as_current_observation( as_type="span", name="chat-round-workflow", input={"user_query": user_query} ) as root_span: with propagate_attributes( user_id="chat-user-001", session_id="chat-session-001", tags=["chat-demo", "glm", "interactive"] ): resp = llm_client.chat.completions.create( model=LLM_MODEL, messages=messages, temperature=0.7, extra_body={"return_usage": True} ) answer = resp.choices[0].message.content # 获取token if resp.usage: prompt_tok = resp.usage.prompt_tokens comp_tok = resp.usage.completion_tokens total_tok = resp.usage.total_tokens else: prompt_tok = count_message_tokens(messages) comp_tok = count_text_tokens(answer) total_tok = prompt_tok + comp_tok # generation埋点,usage_details保证token展示 with lf_client.start_as_current_observation( as_type="generation", name="glm-chat-call", model=LLM_MODEL, model_parameters={"temperature": 0.7}, input=messages ) as gen_obs: gen_obs.update( output={"content": answer}, usage_details={ "prompt_tokens": prompt_tok, "completion_tokens": comp_tok, "total_tokens": total_tok } ) root_span.update(output={"answer": answer}) lf_client.flush() return answer, (prompt_tok, comp_tok, total_tok) if __name__ == "__main__": print("✅ Langfuse交互式对话启动 | 输入exit退出,输入clear清空上下文") # 多轮对话上下文 chat_messages = [ {"role": "system", "content": "你是AI助手,回答简洁准确"} ] while True: try: user_text = safe_input("\n👤 你:") except EOFError: print("\n⚠️检测到EOF,重新输入问题") continue except KeyboardInterrupt: print("\n👋 用户中断,退出对话") break cmd = user_text.strip().lower() if cmd == "exit": print("👋 退出对话") break if cmd == "clear": chat_messages = [{"role": "system", "content": "你是AI助手,回答简洁准确"}] print("🧹上下文已清空") continue if not cmd: continue # 追加用户消息 chat_messages.append({"role": "user", "content": user_text}) ans, tok = chat_round(chat_messages) # 追加AI回复到上下文 chat_messages.append({"role": "assistant", "content": ans}) print(f"🤖 AI:{ans}") print(f"📊 Token统计|输入:{tok[0]} 输出:{tok[1]} 总计:{tok[2]}")

浙公网安备 33010602011771号