const{createApp}=Vue const{createVuetify,useGoTo,useDisplay}=Vuetify var data={alert:{show:false,color:'success',text:'',timeout:0,},theme:{dark:false,},nav:{showDrawer:false,showTOC:true,tocPanel:0,tab:'account',post:{discussionId:6971,currentPage:1,targetPage:1,total:15,anchor:0,goToOptions:{container:null,duration:0,easing:'easeInOutCubic',offset:-100,},worker:null,task:[],active:[],apiLock:[],originLike:new Map([]),},related:{block:1,}},search:{width:80,text:null,loading:false,},tags:[{id:30,url:'/t/6c1e511e6a045c7460771613430f',name:'Python',color:'#FFA726',icon:'mdi-language-python',},],posts:[{id:87061,num:0,uid:9953,content:'之前没写过 python ,现在领导指定框架使用 langchain ,要对接市面上多种大模型实现对话能力,当然还有业务上的一些东西。\u003Cbr\u003E我的想法是在 github 上找一个有完整对话功能实现的 python 项目下来学习学习顺便改改,但是上边的项目好像都很大了,对我来说要剥离出来不容易。\u003Cbr\u003E有没有 v 友看过一些比较简易或者只有对话功能的项目,分享一下',ipRegion:'',updatedByUid:0,createdAt:'2025-03-28 10:02:30',updatedAt:'2025-03-31 10:45:14',mentionNum:0,mentionedBy:[],mentionUsers:[],likeUsers:[],},{id:87062,num:1,uid:6454,content:'使用 langchain ,扣 5 分',ipRegion:'',updatedByUid:0,createdAt:'2025-03-28 10:16:14',updatedAt:'2025-03-31 10:45:14',mentionNum:0,mentionedBy:[2,],mentionUsers:[],likeUsers:[],},{id:87063,num:2,uid:9953,content:'#1 哈哈哈哈没办法,领导是不懂的,我也不懂没法给建议,他就是听到这个可以就用这个,无奈',ipRegion:'',updatedByUid:0,createdAt:'2025-03-28 10:18:39',updatedAt:'2025-03-31 10:45:14',mentionNum:1,mentionedBy:[],mentionUsers:[],likeUsers:[],},{id:87064,num:3,uid:10837,content:'看到 langchain 心里一惊,想着那么**的东西也有人用。再次确定后发现我们公司付费用的叫 LangChatPro 。',ipRegion:'',updatedByUid:0,createdAt:'2025-03-28 10:44:02',updatedAt:'2025-03-31 10:45:14',mentionNum:0,mentionedBy:[],mentionUsers:[],likeUsers:[],},{id:87065,num:4,uid:4704,content:'应该是用 langgraph 吧?',ipRegion:'',updatedByUid:0,createdAt:'2025-03-28 10:47:25',updatedAt:'2025-03-31 10:45:14',mentionNum:0,mentionedBy:[],mentionUsers:[],likeUsers:[],},{id:87066,num:5,uid:12282,content:'langchain 本身就支持各种模型,可以看 chat_models 模块,还有现在大模型的参数基本都是一致的,有 one-api 、new-api 这种模型 api 集成平台,可以接入市面上几乎所有模型',ipRegion:'',updatedByUid:0,createdAt:'2025-03-28 10:48:03',updatedAt:'2025-03-31 10:45:14',mentionNum:0,mentionedBy:[],mentionUsers:[],likeUsers:[],},{id:87067,num:6,uid:2745,content:'现在用 pydantic-ai 感觉比较轻量点',ipRegion:'',updatedByUid:0,createdAt:'2025-03-28 10:48:56',updatedAt:'2025-03-31 10:45:14',mentionNum:0,mentionedBy:[],mentionUsers:[],likeUsers:[],},{id:87068,num:7,uid:415,content:'大模型 restful API 几乎都是兼容的,只有 base API 的路径名字不一样,你只需要用 Python 定义个字典,写上 base url 和模型别名就可以了,',ipRegion:'',updatedByUid:0,createdAt:'2025-03-28 12:06:42',updatedAt:'2025-03-31 10:45:14',mentionNum:0,mentionedBy:[],mentionUsers:[],likeUsers:[],},{id:87069,num:8,uid:15690,content:'用 Python 的直接用 open ai 的 SDK 就行,绝大多数的大模型后端都支持 openai 的 sdk ,支持同一套标准,模型名字 base url 和 API key 一改就能无缝衔接。这包括什么 OpenAI, Gemini, deepseek, 智谱, 硅基流动,还有本地的推理引擎,比如 vllm ,ollama ,llamacpp 之类的。\u003Cbr\u003E\u003Cbr\u003E单纯的与 llm 对话一个几十行一百行的 Python 单文件就能实现,一般不会单独写成项目,你上网搜 openai 的相关教程可能能找的到,或是直接让 ai 给你写个案例代码。如果你不熟悉 Python 记得顺便看一下包管理器,依赖管理,虚拟环境之类的东西。2025 年这套东西可以用 uv 解决,不过 ai 可能还不熟悉最新版本的 uv 。\u003Cbr\u003E\u003Cbr\u003E纯对话的最小代码我没有,不过之前写过一个 gist ,向 LLM 提问 1000 次,让他生成随机数,但生成 0 的概率是 90%,运行结果是跑 1000 次之后所有的结果都是 0 (?)。62 行,配置一改就能换到其他大模型去。\u003Cbr\u003E\u003Cbr\u003Ehttps://gist.github.com/t41372/84f250d2ae3567332fc2b97ed4f868a8\u003Cbr\u003E\u003Cbr\u003E关于 langchain ,我不推荐新手用 langchain ,他会让你的生活很痛苦。',ipRegion:'',updatedByUid:0,createdAt:'2025-03-28 13:08:32',updatedAt:'2025-03-31 10:45:14',mentionNum:0,mentionedBy:[10,],mentionUsers:[],likeUsers:[],},{id:87070,num:9,uid:10742,content:'用这个还不如直接用 python 对接 sdk 或者 api',ipRegion:'',updatedByUid:0,createdAt:'2025-03-28 13:15:31',updatedAt:'2025-03-31 10:45:14',mentionNum:0,mentionedBy:[],mentionUsers:[],likeUsers:[],},{id:87071,num:10,uid:9953,content:'#8 好的,谢谢我看看,其实也不一定要用 langchain ,看这么多人嫌弃我也知道肯定不是最优了哈哈哈',ipRegion:'',updatedByUid:0,createdAt:'2025-03-28 13:37:08',updatedAt:'2025-03-31 10:45:14',mentionNum:8,mentionedBy:[],mentionUsers:[],likeUsers:[],},{id:87072,num:11,uid:8184,content:'好奇 langchain 怎么了,很久没关注了,我记得之前接触的时候 langchain 还是应用开发的首选',ipRegion:'',updatedByUid:0,createdAt:'2025-03-28 15:23:00',updatedAt:'2025-03-31 10:45:14',mentionNum:0,mentionedBy:[12,],mentionUsers:[],likeUsers:[],},{id:87073,num:12,uid:9953,content:'#11 不知道耶,用过的 v 有都很嫌弃的样子,我还是直接选择 openai 了',ipRegion:'',updatedByUid:0,createdAt:'2025-03-28 15:50:47',updatedAt:'2025-03-31 10:45:14',mentionNum:11,mentionedBy:[],mentionUsers:[],likeUsers:[],},{id:87074,num:13,uid:15691,content:'go 也有 langchain ,聚合所有模型不是客户端搞得,类似你少一个 openruter 的网关',ipRegion:'',updatedByUid:0,createdAt:'2025-03-28 16:30:32',updatedAt:'2025-03-31 10:45:14',mentionNum:0,mentionedBy:[],mentionUsers:[],likeUsers:[],},{id:87075,num:14,uid:12276,content:'市面上的模型基本都提供 openai 接口格式的请求,所以可以直接用 openai 的 sdk ,改下 base_url 就行了。至于对接多种大模型,那是接口网关之类的东西应该干的事',ipRegion:'',updatedByUid:0,createdAt:'2025-03-28 16:42:18',updatedAt:'2025-03-31 10:45:14',mentionNum:0,mentionedBy:[],mentionUsers:[],likeUsers:[],},],usersMap:new Map([[4704,{uid:4704,url:'/u/110e460c6a045c73676a526f023f6a3f',avatar:'/a/110e460c6a045c73676a526f023f6a3f',username:'EdwardXia🤖',}],[8184,{uid:8184,url:'/u/0b3d0b3d6a045c7f6162526f01240032',avatar:'/a/0b3d0b3d6a045c7f6162526f01240032',username:'litchinn🤖',}],[10742,{uid:10742,url:'/u/1e7958206a045d77676e546f215f1113',avatar:'/a/1e7958206a045d77676e546f215f1113',username:'darksword21🤖',}],[15691,{uid:15691,url:'/u/367154296a045d726663576f3e58242f',avatar:'/a/367154296a045d726663576f3e58242f',username:'funky🤖',}],[9953,{uid:9953,url:'/u/3705050e6a045c7e696f556f35113f36',avatar:'/a/3705050e6a045c7e696f556f35113f36',username:'so2back🤖',}],[6454,{uid:6454,url:'/u/190874206a045c71646f526f27503c11',avatar:'/a/190874206a045c71646f526f27503c11',username:'kerb15🤖',}],[10837,{uid:10837,url:'/u/2d71001d6a045d776869516f265c2435',avatar:'/a/2d71001d6a045d776869516f265c2435',username:'shen13176101🤖',}],[15690,{uid:15690,url:'/u/621e790c6a045d726663566f4e1e2100',avatar:'/a/621e790c6a045d726663566f4e1e2100',username:'t41372🤖',}],[12276,{uid:12276,url:'/u/1e060a1b6a045d75626d506f155b620c',avatar:'/a/1e060a1b6a045d75626d506f155b620c',username:'aeron🤖',}],[12282,{uid:12282,url:'/u/161d755e6a045d756262546f4f0a1d0f',avatar:'/a/161d755e6a045d756262546f4f0a1d0f',username:'supergeek1🤖',}],[415,{uid:415,url:'/u/3d3f79186a045c77646b536f06301237',avatar:'/a/3d3f79186a045c77646b536f06301237',username:'harlen🤖',}],[2745,{uid:2745,url:'/u/2b1166546a045c75676e536f320f1533',avatar:'/a/2b1166546a045c75676e536f320f1533',username:'dearmymy🤖',}],]),related:[{title:'Python 开发工程师(全栈)',url:'/d/6d0454026a045c77606a5675435b636a3d293821',},{title:'技术栈选择: Java 还是 Python',url:'/d/2878683b6a045c77606a5675475d616a083a1603',},{title:'用 PyQt 搞了个日历笔记本',url:'/d/0d11435b6a045c77606a56744f50626a0e081434',},{title:'Typescript 如此成功,为何没有发展出所谓 “Typthon”?',url:'/d/35315c016a045c77606a5674445f626a0e730224',},{title:'为什么 Python 、Node.js 就不能学习一下 C#这种优雅的依赖管理方式?',url:'/d/0f1f5f3d6a045c77606a5674445c676a04311f60',},{title:'郑州招 Python , Node.js 开发岗',url:'/d/082c61146a045c77606a5677425e616a3d706116',},{title:'Python 的 faker 库是不是有问题',url:'/d/0d077b5f6a045c77606a5677425b626a001b1861',},{title:'Python 写了个 GUI 程序,运行久了老是闪退 咋解决',url:'/d/002f00186a045c77606a5677465e6a6a583b3c18',},{title:'今天自己写了个监测医院挂号剩余数量的python脚本',url:'/d/1d3379236a045c77606a5676455f656a1f310037',},{title:'Python自学,有没有什么推荐的学习网站,资料,项目?',url:'/d/373f57266a045c77606a5676455f626a190c1137',},{title:'[北京/上海] 百亿量化公司 C++/ Python /算法/训练/高性能/效能专家',url:'/d/113b00286a045c77606a56714059666a03323803',},{title:'如何在 jupyter notebook 里安装 binance 模块?有没有会 Python 的大神来指点下?',url:'/d/0e2c78186a045c77606a56714358646a54021634',},{title:'这是什么 jupyter 插件,感觉挺酷',url:'/d/683e7a296a045c77606a56704358626a2570262b',},{title:'帧转视频有哪些最快的方案?',url:'/d/627b665f6a045c77606a56704751636a0a311f3d',},{title:'Python 小组作业,做个什么东西好呢?',url:'/d/6801785c6a045c77606a5673425a656a2929261b',},{title:'用 AI 写爬虫,做了个爬取个人社交平台内容并分析的工具',url:'/d/1601671e6a045c77606a56724f59636a0a101739',},],} const App={setup(){const goTo=useGoTo() const{mdAndUp}=useDisplay() return{goTo,mdAndUp}},data(){return data;},mounted(){const themeDark=localStorage.getItem("themeDark") if(themeDark!==null){this.theme.dark=JSON.parse(themeDark)} if(this.nav.post.total>(this.nav.post.currentPage-1)*100+20){let moreLen=100 if(this.nav.post.total({id:null,num:(this.nav.post.currentPage-1)*100+v,uid:null,content:null,ipRegion:null,updatedByUid:null,createdAt:null,updatedAt:null,mentionNum:null,mentionedBy:null,mentionUsers:null,likeUsers:null,})) this.posts.push(...morePosts.slice(20))} this.workerStart() const hash=window.location.hash const match=hash.match(/#(\d+)/) if(match){const n=parseInt(match[1],10) if(n>=(this.nav.post.currentPage-1)*100&&n{this.jumpTo(n)})}} this.$nextTick(()=>{this.addHeadingIds() tocbot.init({tocSelector:'.toc',contentSelector:'#post-content-0',headingSelector:'h2, h3, h4',headingsOffset:100,scrollSmoothOffset:-100,scrollSmooth:true,collapseDepth:6,onClick:function(e){setTimeout(()=>{history.replaceState(null,'',window.location.pathname+window.location.search)},0)},}) tocbot.refresh()});},beforeUnmount(){this.workerStop() if(this.quill){this.quill.destroy() this.quill=null}},computed:{dposts(){return this.posts.slice(20);},},created(){},methods:{successAlert(msg){this.alert={show:true,color:'success',text:msg,timeout:1500,}},failureAlert(msg){this.alert={show:true,color:'error',text:msg,timeout:5000,}},flipThemeDark(){this.theme.dark=!this.theme.dark localStorage.setItem("themeDark",JSON.stringify(this.theme.dark))},toSearch(){if(!this.search.text){this.failureAlert('搜索词不能为空') return} let keywords=this.search.text.trim() if(keywords.length<1){this.failureAlert('搜索词不能为空') return} if(keywords.length>100){this.failureAlert('搜索词过长') return} this.doSearch(keywords)},toReg(){window.location.href="/reg"},toLogin(){window.location.href="/login"},toPage(){let url=window.location.href url=url.replace(/(\/\d+)?(#[0-9]+)?$/,this.nav.post.targetPage>1?`/${this.nav.post.targetPage}`:'') window.location.href=url},toLoadRelated({done}){if(this.my&&this.my.uid){this.apiLoadRelated({done})}else{done('ok')}},workerStart(){this.nav.post.worker=setInterval(()=>{this.workerLoad()},500);},workerStop(){if(this.nav.post.worker){clearInterval(this.nav.post.worker);this.nav.post.worker=null;}},async jumpTo(num){const page=Math.floor(num/100)+1 const i=num-(page-1)*100 if(page===this.nav.post.currentPage){this.goTo("#post-"+num,this.nav.post.goToOptions) if(!this.posts[i].id){const block=Math.floor(num/20)+1 this.nav.post.apiLock[block]=true await this.apiLoadPosts(block) this.$nextTick(()=>{this.goTo("#post-"+num,this.nav.post.goToOptions)})}}else{let url=window.location.href url=url.replace(/(\/\d+)?(#[0-9]+)?$/,page>1?`/${page}`:'') url=url+"#"+num window.location.href=url}},postIntersect(num){return(isIntersecting,entries,observer)=>{if(isIntersecting){this.nav.post.task.push(num) this.nav.post.active.push(num) this.nav.post.active=this.nav.post.active.filter(item=>Math.abs(item-num)<=5) this.nav.post.active.sort((a,b)=>a-b)}else{this.nav.post.active=this.nav.post.active.filter(item=>item!==num)} if(this.nav.post.active[0]){this.nav.post.anchor=this.nav.post.active[0]}else{this.nav.post.anchor=0}}},async apiLoadPosts(block){try{const response=await axios.post('/fapi/v1/post/block/'+block,{discussionId:this.nav.post.discussionId,}) if(response.data.code===0){response.data.data.posts.forEach(post=>{const i=post.num%100 Object.assign(this.posts[i],post)}) response.data.data.users.forEach(user=>{this.usersMap.set(user.uid,user)})}else{this.failureAlert('回帖数据加载失败: '+response.data.msg)}}catch(error){this.failureAlert('回帖数据加载失败: '+error)} this.nav.post.apiLock[block]=false},workerLoad(){while(this.nav.post.task.length){const num=this.nav.post.task.pop() const i=num-(this.nav.post.currentPage-1)*100 if(!this.posts[i].id){const block=Math.floor(num/20)+1 if(!this.nav.post.apiLock[block]){this.nav.post.apiLock[block]=true this.apiLoadPosts(block)}}}},getTimeInfo(t){if(!t){return ""} const now=new Date();const then=new Date(t);const diff=now-then;const minute=60*1000;const hour=minute*60;const day=hour*24;const month=day*30;const year=month*12;if(diffpost.num===num) if(!post){return "#"+num} const uid=post.uid const username=this.usersMap.get(uid)?.username if(!username){return "#"+num} return username},getUsernameByPostId(id){const post=this.posts.find(post=>post.id===id) if(!post){return "#"+this.getPostNumByPostId(id)} const uid=post.uid const username=this.usersMap.get(uid).username if(!username){return "#"+this.getPostNumByPostId(id)} return username},getPostNumByPostId(id){const post=this.posts.find(post=>post.id===id) return post.num},getPostById(id){const post=this.posts.find(post=>post.id===id) return post},getPostByNum(num){const post=this.posts.find(post=>post.num===num) return post},getAvatarByUid(uid){const avatar=this.usersMap.get(uid)?.avatar if(!avatar){return this.getRandomAvatar()} return avatar},getAvatarByPostNum(num){const post=this.posts.find(post=>post.num===num) if(!post){return this.getRandomAvatar()} const uid=post.uid return this.getAvatarByUid(uid)},getRandomAvatar(){const num=Math.floor(Math.random()*100) return "https://randomuser.me/api/portraits/men/"+num+".jpg"},getUrlByUid(uid){const url=this.usersMap.get(uid)?.url if(!url){return ""} return url},getTextByPostNum(num){const post=this.posts.find(post=>post.num===num) if(!post||!post.content){return '点击跳转到#'+num+'查看'} const parser=new DOMParser() const doc=parser.parseFromString(post.content,'text/html') const text=doc.body.textContent||'' return text.slice(0,100)},addHeadingIds(){const content=document.getElementById('post-content-0') if(!content){this.nav.showTOC=false return} const headings=content.querySelectorAll('h2, h3, h4') headings.forEach((heading,index)=>{if(!heading.id){heading.id=`toc-nav-${index}`}}) if(headings.length==0){this.nav.showTOC=false}},async doSearch(keywords){this.search.loading=true try{const response=await axios.post('/fapi/v1/search',{keywords:keywords,}) if(response.data.code===0){if(response.data.data.hash&&response.data.data.hash.length===32){window.location.href="/s/"+response.data.data.hash}else{this.failureAlert('搜索失败: 搜索服务异常')}}else{this.failureAlert('搜索失败: '+response.data.msg)}}catch(error){this.failureAlert('搜索失败: '+error)} this.search.loading=false},debounce(fn,delay){let timer=null return function(...args){if(timer)clearTimeout(timer) timer=setTimeout(()=>{fn.apply(this,args)},delay);};},},watch:{'nav.post.targetPage':{handler:async function(newV,oldV){this.toPage()},immediate:false},},} const vuetify=createVuetify({defaults:{global:{ripple:true,},},}) const app=createApp(App) app.use(vuetify).mount("#app")