diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2c96e81 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.env +.jbeval \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/99_react_agent.py b/99_react_agent.py new file mode 100644 index 0000000..3debcb9 --- /dev/null +++ b/99_react_agent.py @@ -0,0 +1,202 @@ +""" +ReAct Agent(基于 DeepSeek Function Calling 实现) + +ReAct = Reasoning + Acting:模型先"想"(决定是否调用工具、调用哪个), +再"做"(执行工具),拿到"观察结果"后继续下一轮思考,直到给出最终答案。 +""" + +import os +import json +from openai import OpenAI +from dotenv import load_dotenv + +load_dotenv() + +client = OpenAI( + api_key=os.environ.get("DEEPSEEK_API_KEY"), + base_url="https://api.deepseek.com", +) + +MODEL = "deepseek-chat" # 换成你实际可用的模型名 +MAX_STEPS = 8 # 防止死循环的最大推理步数 + + +# ============================================================ +# 1. 工具实现层:每个工具就是一个普通的 Python 函数 +# ============================================================ +def get_weather(location: str) -> str: + """模拟天气查询接口""" + fake_db = { + "杭州": "30℃,晴,东南风 2 级", + "北京": "25℃,多云", + "上海": "28℃,小雨", + } + return fake_db.get(location, f"{location}:暂无该城市天气数据") + + +def calculator(expression: str) -> str: + """一个安全的四则运算计算器""" + allowed = set("0123456789+-*/(). ") + if not set(expression) <= allowed: + return "错误:表达式包含非法字符" + try: + return str(eval(expression, {"__builtins__": {}}, {})) + except Exception as e: + return f"计算失败:{e}" + + +# 工具名 -> 函数 的注册表,Agent 靠它做分发 +TOOL_REGISTRY = { + "get_weather": get_weather, + "calculator": calculator, +} + + +# ============================================================ +# 2. 工具描述层:告诉模型有哪些工具可用(JSON Schema) +# ============================================================ +TOOLS_SCHEMA = [ + { + "type": "function", + "function": { + "name": "get_weather", + "description": "获取指定城市的当前天气", + "parameters": { + "type": "object", + "properties": { + "location": {"type": "string", "description": "城市名,如杭州"}, + }, + "required": ["location"], + }, + }, + }, + { + "type": "function", + "function": { + "name": "calculator", + "description": "计算一个数学表达式,例如 '30 - 25'", + "parameters": { + "type": "object", + "properties": { + "expression": {"type": "string", "description": "要计算的数学表达式"}, + }, + "required": ["expression"], + }, + }, + }, +] + + +# ============================================================ +# 3. 系统提示词:定义 Agent 的行为方式 +# ============================================================ +SYSTEM_PROMPT = """你是一个可以使用工具的智能助手。 + +工作要求: +1. 先思考问题需要哪些信息,判断是否需要调用工具。 +2. 需要外部信息(天气、计算等)时,必须调用工具,不要凭空编造。 +3. 可以连续多次调用工具;每一步只做当前最必要的事。 +4. 拿到工具结果后,用简洁的中文给出最终答案。 +""" + + +# ============================================================ +# 4. 核心:ReAct 循环 +# ============================================================ +def run_react_agent(user_query: str, max_steps: int = MAX_STEPS, verbose: bool = True): + """ + 执行 ReAct 循环: + 思考(Thought) -> 行动(Action) -> 观察(Observation) -> 再思考 ... -> 最终答案 + 返回 (最终答案, 完整消息历史) + """ + messages = [ + {"role": "system", "content": SYSTEM_PROMPT}, + {"role": "user", "content": user_query}, + ] + + for step in range(1, max_steps + 1): + if verbose: + print(f"\n{'=' * 20} Step {step} {'=' * 20}") + + # ---------- Thought:让模型决定下一步做什么 ---------- + response = client.chat.completions.create( + model=MODEL, + messages=messages, + tools=TOOLS_SCHEMA, + tool_choice="auto", + ) + assistant_msg = response.choices[0].message + + # 把模型的回复写入历史(必须手动构造,content 为 None 时补成 "") + msg_dict = {"role": "assistant", "content": assistant_msg.content or ""} + if assistant_msg.tool_calls: + msg_dict["tool_calls"] = [tc.model_dump() for tc in assistant_msg.tool_calls] + messages.append(msg_dict) + + if verbose and assistant_msg.content: + print(f"[Thought] {assistant_msg.content}") + + # ---------- 终止条件:模型不再请求工具,直接给出答案 ---------- + if not assistant_msg.tool_calls: + final_answer = assistant_msg.content or "" + if verbose: + print(f"[Final] {final_answer}") + return final_answer, messages + + # ---------- Action + Observation:执行所有工具调用 ---------- + for tool_call in assistant_msg.tool_calls: + name = tool_call.function.name + + # 注意:arguments 是 JSON 字符串,必须解析成 dict 再传参 + try: + args = json.loads(tool_call.function.arguments or "{}") + except json.JSONDecodeError: + args = {} + + if verbose: + print(f"[Action] {name}({json.dumps(args, ensure_ascii=False)})") + + func = TOOL_REGISTRY.get(name) + if func is None: + result = f"错误:未知工具 {name}" + else: + try: + result = func(**args) + except Exception as e: + result = f"工具执行异常:{e}" + + result = str(result) + if verbose: + print(f"[Observation] {result}") + + # 工具结果必须带上 tool_call_id,一一对应 + messages.append({ + "role": "tool", + "tool_call_id": tool_call.id, + "content": result, + }) + + # 超过最大步数仍未收敛 + fallback = "抱歉,我在限定步骤内没能得出结论。" + if verbose: + print(f"[Final] {fallback}") + return fallback, messages + + +# ============================================================ +# 5. 运行入口 +# ============================================================ +if __name__ == "__main__": + # 单轮测试 + for q in ["杭州天气怎么样?", "杭州和北京温差多少度?"]: + print(f"\n\n########## 用户:{q} ##########") + answer, history = run_react_agent(q) + print(f"回答:{answer}") + + # 如果想做成多轮对话的交互式 Agent,可以这样: + # while True: + # q = input("\n你:") + # if q in ("exit", "quit"): + # break + # answer, _ = run_react_agent(q) + # print("助手:", answer) \ No newline at end of file diff --git a/tets.py b/tets.py new file mode 100644 index 0000000..8c9a920 --- /dev/null +++ b/tets.py @@ -0,0 +1,41 @@ +import base64 +from openai import OpenAI +api_key='sk-83c3ebe6b1b04bdf93c3c4be9b17736d' +client = OpenAI(api_key=api_key, base_url="https://api.deepseek.com") +files = client.files.list() +for f in files.data: + print(f.id, f.filename) +with open("zky.png", "rb") as f: + b64 = base64.b64encode(f.read()).decode("utf-8") + +response = client.chat.completions.create( + model="deepseek-flash", + messages=[ + { + "role": "user", + "content": [ + {"type": "text", "text": "这张图片里有什么?"}, + { + "type": "image_url", + "image_url": {"url": f"data:image/jpeg;base64,{b64}"}, + }, + ], + } + ], +) +print(response.choices[0].message.content) + + +response = client.chat.completions.create( + model="deepseek-flash", + messages=[ + { + "role": "user", + "content": [ + {"type": "text", "text": "这张图片里有什么?"}, + {"type": "file", "file_id": "file-api-01ab723a-0bd1-4e38-b373-a25278eb3baf"}, + ], + } + ], +) +print(response.choices[0].message.content) \ No newline at end of file diff --git a/w1_d1/git.md b/w1_d1/git.md new file mode 100644 index 0000000..db85211 --- /dev/null +++ b/w1_d1/git.md @@ -0,0 +1,412 @@ +## git介绍 + +- 思考1:任何项目,是否有多个版本?如果是,每个版本的代码是否需要保存? + - 有多个版本,每个版本的代码都要保存。如果没有其他工具可能就是一个文件夹一个版本,那样版本一多就乱了 +- 思考2:公司里的项目一般是多人开发的,要不要共享代码? + - 要共享代码,但是传统共享代码,就是微信传递、u盘传递等 +- 以上问题,都可以由`git`这个工具解决 +- git是什么? + - 一款分布式的版本管理、多人协作工具 +- 集中式版本管理 + - 一个团队的代码,全部集中存到一个服务器上。所以如果这个服务器挂,版本都没了 +- 分布式版本管理 + - 一个团队的代码,整体是存到服务器上的,但是当某个人下载这份代码,会相当于下载了全部的副本,这就叫分布式。好处:数据不易丢失 +- 总结:git是什么? + - 就是一款分布式的版本管理、多人协作的工具 + - 作用1:控制和记录版本变化 + - 作用2:方便多人协作共享代码 + - ....... + +## git下载与安装 + +[官网](https://git-scm.com/downloads) + +- 下载完了后就建议下一步下一步直到完成,毛都不要改 +- 建议:开发软件就建议装在C盘 +- 如何判断安装成功? + - windows系统可以:右键看有没有 `git bash here`,有就代表成功 + - 或者windows和mac都可以在`终端`(cmd、小黑窗),输入git按回车,如果出现一大坨英文,就代表成功 + +## git 配置用户名与邮箱 + +- 作用:用来标识自己是谁 + +- 配置方法: + - 打开小黑窗 + - 方法1:右键开始菜单,然后点运行,然后输入cmd按回车 + - 快捷键winows键+r再输入cmd回车 + - 方法2: + - 随便打开一个文件夹,在地址栏上输入cmd按回车 + - mac直接搜“终端”两个字 +- 命令如下 + +```Bash +# git config user.name 你的目标用户名 +# git config user.email 你的目标邮箱名 + +# 使用--global参数,配置全局的用户名和邮箱,只需要配置一次即可 +# 用户名有意义即可 +# 邮箱要真实可用 +git config --global user.name mm +git config --global user.email mm@mmm.com + +# 查看配置信息 +git config --list + +# 取消配置 - 把它们从配置文件里删掉 - 一般不用 +git config --unset --global user.name +git config --unset --global user.email +``` + +- 总结: + - 把`gitee`上的用户名和邮箱配置到git上 + - 要配置几次? + - 1次 + - 有什么用? + - 可以给服务器识别你的身份 + - 命令为 + +```Bash +git config --global user.name 用户名 +git config --global user.email 邮箱 +``` + +## 初始化仓库 + +- git作用:可以做版本管理,相当于游戏里的`存档` +- 并不是说你电脑上安装了git,那么所有的项目都被管理了 +- 而是你要给这个项目做**初始化仓库**的操作,才代表这个项目被git管理了 +- 如果希望一个项目被`git`要用 `git init`初始化这个项目 +- 步骤: + - 来到这个项目文件夹,右键`git bash here`会弹出一个专门给`git`用的小黑窗,而且直接就在这个项目的路径上,输入 + +```Bash +git init +- 敲完会在这个项目文件夹里多一个 `.git`的隐藏文件夹,所以如果以后在项目中看到有这个文件夹,代表这个项目已经被`git`管理,就不用再初始化了 +``` + +- 注意: + + `.git`文件夹不能删了,删了就相当于所有的`版本`都没了,只留下当前代码 + + - 有些人即使初始化了也看不到这个文件夹,是因为系统设置了隐藏文件夹不可见,所以可以通过下图设置 + + ![image-20230217113139174](https://typorapic-1306801437.cos.ap-guangzhou.myqcloud.com/image-20230217113139174.png) + + - 请问一个项目`搞一次`,而且这个项目如果已经有了,就不搞 + +## git - 记录改动 + +![image-20230216213606771](https://typorapic-1306801437.cos.ap-guangzhou.myqcloud.com/image-20230216213606771.png) + +- 作用:相当于就是产生`存档` +- git三大区域: + - 工作区:就是指项目代码 + - 暂存区和仓库区:对我们而言是不可见的 +- 如果你`工作区`改动了代码,想产生一个`存档`,就要先把改动加到`暂存区`,再加到`仓库区` +- 先改代码,然后敲命令 + +```Bash +# 查看当前状态 +git status + +# 把当前改动到的所有文件都加到暂存区 +git add . +# 添加某个文件到暂存区 -- 用的少 +git add 文件名 + +# 把暂存区的内容提交到仓库区 +git commit -m'本次操作的说明文字' +``` + +- 如果仓库此时没有任何新的改动,`git status`会出现`干净`的提示 + + ![image-20230217114048810](https://typorapic-1306801437.cos.ap-guangzhou.myqcloud.com/image-20230217114048810.png) + +## git - 快速提交的说明 + +```Bash +git commit -a -m '提交说明' +``` + +- 仅能用于本就存在于仓库的文件 +- 仓库中新建的文件,还是得先加到`暂存区`,再提交到`仓库区` + +## git - 修改已提交的说明文字 + +- 如果刚刚提交的文字不小心写错了,可以修改 + +```Bash +git commit --amend +``` + +- 此时会进入到编辑模式,默认的编辑器非常难用 + - 进入后你需要输入`i`,代表进入修改,然后通过键盘上下左右改动光标位置,再把你需要的文字删掉或改掉 + - 改完后,还得按一下`esc`键,然后英文状态下输入 `:wq`按回车才代表修改完成 + - ``` + git config --global core.editor "notepad" + ``` + + + +## git - 查看记录 + +```Bash +# 查看详细记录 +git log + +# 一行显示每条记录 +git log --oneline +``` + +## git - 回滚记录 + +- 作用:可以把代码恢复到某个记录的状态 +- 命令 + +```Bash +git reset --hard 记录的hash值(记录的id) +- 如何获取记录的hash值? +git log +# 用的多 +git log --oneline +``` + +- 注意: + - 回滚到某个记录后,你再`git log`只能看到当前记录和它之前的,后面的看不到 + - 要想看到全部操作记录,用 + +```Bash +git reflog +``` + +- 练习: + - 要求产生至少3个记录 + - 能自由切换3个记录的代码 + - 最后一次一定要回滚到最新记录 + +## git - 配置被git忽略的文件 + +- 有些文件可能不需要被git管理,例如一些自己手写的笔记,密码等,那就可以配置忽略文件 +- 需要在仓库根目录,新建 `.gitignore`文件,在这个文件里写需要被忽略的文件,例 + +```JavaScript +密码.md +情书.md +小电影 +``` + +- 如果这些文件被git忽略,将不会被提交,也不受git记录变化而变化 + +## git - 分支的概念 + +- 所谓分支,就是指把当前的代码克隆一份 +- 这样做的好处是:可以按员工开分支代码,一人负责一个分支,好处是方便管理,以及某一个分支有问题绝对不影响其他分支 +- git初始化后,默认就会有一个分支叫`master`,我们也称之为主分支 +- 开发中有个规范:**主分支上不直接进行开发**,它里面只保留经过测试的最稳定的版本代码,开发不会直接在这个分支上进行 +- 所以还会在主分支的基础上克隆出一份代码(开一个新的分支),分支名叫`develop`或者`dev`,代表是专门用来开发的分支 +- 再然后在 `develop`基础上又开多个分支,不同的人负责不同的分支,他们写完代码,又各自合并到develop分支,然后经过大量测试和试运行发现很稳定,就合并到 `master`产生一个稳定的版本 + +![image-20230217162251228](https://typorapic-1306801437.cos.ap-guangzhou.myqcloud.com/image-20230217162251228.png) + +## git - 分支操作 + +- 命令如下 + +```Bash +# 查看当前所有分支 +git branch +# 新建分支 +git branch 分支名 +# 切换分支 +git checkout 分支名 + +# 新建分支并切换过去 +git checkout -b 分支名 +``` + +- 合并分支一定要小心(要找对分支、确保写的代码都已经提交了),再切换到想合并的目标分支 + - 比如我想合并到`develop`,我就要先切换到`develop` + +```Bash +git checkout develop +``` + +- 合并语法 + +```Bash +git merge 分支名 +# 例如:当前是develop分支,然后你写git merge zs,意思就是把zs分支的代码合并到develop里面 +git merge zs +``` + +- 练习要求: + - 默认有master分支,需要新建develop分支,再然后在develop基础上创建`dy`、`ls` + - `dy`写`dy`,`ls`写`ls`的代码(新建dy.html,ls.html) + - 每个分支写完了先提交到当前分支 + - 然后合并到develop + - 等develop有所有代码了,再合并到`master` + +## git - 分支合并冲突 + +- 工作中必须学会如何解决冲突 + +![img](https://typorapic-1306801437.cos.ap-guangzhou.myqcloud.com/image-20230217171545728.png) + +- 当不同分支负责人员改动到同一个文件,合并时就会产生冲突 + +- 如果冲突了,在vscode中将看到如下界面 + + ![](https://typorapic-1306801437.cos.ap-guangzhou.myqcloud.com/image-20230217171754089.png) + +- 三个选项具体怎么选要看需求 + + - 例如1:当前的有bug,所以领导指派李四去修复bug,然后合并过来,肯定是保留李四,也就是保留合并过来的(点第二个按钮) + - 例如2: 如果当前分支代码比较多,还包含了要合并过来的分支的代码,就选第一个 + - 例如3:如果两段代码分别是完成不同的功能,而且这两个功能都要,就选第三个都保留 + +## git - 删除分支 + +```Bash +git branch -d 分支名 +# 强行删 +git branch -D 分支名 +``` + +- 如果你要删某个分支,你要确保不在这个分支上,才可以删 +- 如果被删的分支有新的记录,但是还没有合并,那么也删不掉,用`git branch -D 分支名` + + + +## 远程仓库 - 建库、删库 + +- 建库 + +![image-20230218094506675](https://typorapic-1306801437.cos.ap-guangzhou.myqcloud.com/image-20230218094506675.png) + +- 删库 + + ![image-20230218094553000](https://typorapic-1306801437.cos.ap-guangzhou.myqcloud.com/image-20230218094553000.png) + + ![image-20230218094630995](https://typorapic-1306801437.cos.ap-guangzhou.myqcloud.com/image-20230218094630995.png) + +## 远程仓库 - 添加团队成员 + +![image-20230218095123406](https://typorapic-1306801437.cos.ap-guangzhou.myqcloud.com/image-20230218095123406.png) + +## 远程仓库 - 推送 + +- 让本地仓库跟远程仓库建立链接 + +```Bash +git remote add 起一个远程仓库名字 仓库地址 +git remote add origin https://gitee.com/xpzll/viewdemo95.git +``` + +- 推送代码到远程仓库 + +```Bash +git push -u origin "master" +``` + +- 以上两段命令,gitee上可以复制 +- 第一次推送会需要登录,但是这个登录只要成功一次,后面都不用登录 + +![image-20230218095746921](https://typorapic-1306801437.cos.ap-guangzhou.myqcloud.com/image-20230218095746921.png) + +## 远程仓库 - 开源 + +![image-20230218095939726](https://typorapic-1306801437.cos.ap-guangzhou.myqcloud.com/image-20230218095939726.png) + +## 远程仓库 - 多远程仓库连接 + +```Bash +# 建立链接 +git remote add origin 仓库地址 +``` + +- remote是远程的意思 +- add是添加的意思 +- origin只是给这个远程仓库的连接起了一个名字叫origin + +```Bash +# 把本地仓库推送到 origin 连接的远程仓库,并且也把远程仓库分支叫为master +git push -u origin "master" +``` + +- 所以如果你一个本地仓库,要推送多个远程仓库,要建立新的连接,而新的连接名字不能重复 + +```Bash +# 也就是说连接到这个新仓库的别名叫xx +git remote add xx 新仓库地址 + +## 意思是把本地仓库推送到xx这个连接的远程仓库 +git push -u xx "master" +``` + +## 远程仓库 - 删除连接 + +- 工作中一般一个本地仓库只连接一个远程仓库 +- 问题来了如果之前我在本地建立了一个连接,而且连接名字叫origin,后面再建立连接除非改名字,不然还叫origin会报错 +- 但是我就是想用origin,就把之前的origin删掉 + +```Bash +git remote remove origin +``` + +## 远程仓库 - 克隆 + +- 作用:把远程仓库的代码克隆下来 + +```Bash +# 要么这个仓库是开源,要么你是这个仓库的成员 +git clone 仓库地址 +``` + +## 远程仓库 - 拉取 + +- 作用:可以把远程仓库最新的代码拉取下来 + +```Bash +git pull +``` + +## 解决推送冲突 + +![img](https://typorapic-1306801437.cos.ap-guangzhou.myqcloud.com/image-20230218111524877.png) + +- 解决办法: + - 先`git pull`拉取最新代码 + - 再`git push` +- 注意: + - 为什么以后只要写 `git push`,因为完整写法是之前空仓库用一次就够了,后面都不是空仓库,直接`push` + +## windows的凭据管理器 + +- 它把密码保存到windows的凭据管理器里了 +- 如果你不想用了,就去里面删除,如果删除了下次还得输入 +- 最多就是去里面修改它,因为我可能把gitee账号密码给改了,那这里也得修改 +- 或者如果公司刚好也是用gitee,而且公司没给你发电脑,你用的还是自己的电脑,而且还要满足公司的gitee没用你的邮箱,而是直接给你指派了邮箱,这时候就去要修改 + +## SSH私钥与公钥方式 + +- 因为有人的电脑因为系统原因是没有凭据管理的,那就意味着每次都要输入账号和密码 + +- 所以还有一种方式叫使用`私钥`配合`公钥`的形式来免登录 + + ![image-20230218120707585](https://typorapic-1306801437.cos.ap-guangzhou.myqcloud.com/image-20230218120707585.png) + +- 在任意小黑窗里输入如下命令 + +```Bash +ssh-keygen -t ed25519 -C "你的邮箱地址" +``` + +- 以后去到公司,公司负责人会问你要你的邮箱,或者问你要公钥 +- 如果是要邮箱,就要用`https`,然后输入账号和密码来登录 +- 如果问你要公钥,你就在电脑上生成公钥和私钥,再把公钥部分的内容发给负责人 +- 公钥文件以 `.pub`结尾,去`~/.ssh`文件夹找文件 +- ~代表自己电脑的个人文件夹 +- 例如: `C:\Users\你电脑的用户名\.ssh` +- 如果是账号密码登录用 `https`的仓库地址 +- 如果是公钥用`ssh`的仓库地址 \ No newline at end of file diff --git a/w1_d2/deepseek_api.py b/w1_d2/deepseek_api.py new file mode 100644 index 0000000..50d7261 --- /dev/null +++ b/w1_d2/deepseek_api.py @@ -0,0 +1,6 @@ +import json +b={1:2} +a='{"name":null}' +b.get(1) +j=json.loads(a) +print(j.get('name')) \ No newline at end of file diff --git a/w1_d2/llm-api-demo/01_deepseek_demo.py b/w1_d2/llm-api-demo/01_deepseek_demo.py new file mode 100644 index 0000000..6f33bb9 --- /dev/null +++ b/w1_d2/llm-api-demo/01_deepseek_demo.py @@ -0,0 +1,21 @@ +# Please install OpenAI SDK first: `pip3 install openai` +import os +from openai import OpenAI +from dotenv import load_dotenv +load_dotenv() +client = OpenAI( + api_key=os.environ.get('DEEPSEEK_API_KEY'), + base_url="https://api.deepseek.com") + +response = client.chat.completions.create( + model="deepseek-v4-pro", + messages=[ + {"role": "user", "content": "Hello"}, + ], + stream=False, + reasoning_effort="high", + extra_body={"thinking": {"type": "enabled"}} +) + +print(response.model_dump_json(indent=2)) +print(response.choices[0].message.content) \ No newline at end of file diff --git a/w1_d2/llm-api-demo/02_qwen_demo.py b/w1_d2/llm-api-demo/02_qwen_demo.py new file mode 100644 index 0000000..9d3c744 --- /dev/null +++ b/w1_d2/llm-api-demo/02_qwen_demo.py @@ -0,0 +1,23 @@ +# https://docs.bailian.console.aliyun.com/zh/model-studio/qwen-api-via-openai-chat-completions +import os +from openai import OpenAI +from dotenv import load_dotenv +load_dotenv() +WorkspaceId='llm-zw41x57m8nuezmhp' + +client = OpenAI( + # 若没有配置环境变量,请用百炼API Key将下行替换为:api_key="sk-xxx" + api_key=os.getenv("DASHSCOPE_API_KEY"), + base_url=f"https://{WorkspaceId}.cn-beijing.maas.aliyuncs.com/compatible-mode/v1", +) + +completion = client.chat.completions.create( + # 模型列表:https://help.aliyun.com/zh/model-studio/getting-started/models + model="deepseek-v4.1-flash", + messages=[ + {"role": "system", "content": "You are a helpful assistant."}, + {"role": "user", "content": "你是谁?"}, + ] +) +print(completion.model_dump_json(indent=2)) +print(completion.choices[0].message.content) \ No newline at end of file diff --git a/w1_d2/llm-api-demo/03_streaming_demo.py b/w1_d2/llm-api-demo/03_streaming_demo.py new file mode 100644 index 0000000..f074177 --- /dev/null +++ b/w1_d2/llm-api-demo/03_streaming_demo.py @@ -0,0 +1,37 @@ +import os +from openai import OpenAI +from dotenv import load_dotenv + +load_dotenv() + +client = OpenAI( + api_key=os.environ.get("DEEPSEEK_API_KEY"), + base_url="https://api.deepseek.com", +) + +response = client.chat.completions.create( + model="deepseek-v4-flash", + messages=[ + {"role": "system", "content": "You are a helpful assistant."}, + {"role": "user", "content": "请用三句夸赞一下大帝,大帝是我爸爸"} + ], + stream=True, # 开启流式输出 + # 不设置 reasoning_effort 和 extra_body 中的 thinking,即不使用深度思考 + reasoning_effort="high", + extra_body={"thinking": {"type": "enabled"}} +) + +# count = 0 +# for chunk in response: +# content = chunk.choices[0].delta.content +# if content: +# count += 1 +# print(f"[{count}] {content}") + +# 逐块输出内容 +for chunk in response: + print(chunk) + # ChatCompletionChunk(id='c2291fe4-6757-4de8-a7bb-5033f6a8bd03', choices=[Choice(delta=ChoiceDelta(content='', function_call=None, refusal=None, role='assistant', tool_calls=None), finish_reason=None, index=0, logprobs=None)], created=1790050006, model='deepseek-flash', object='chat.completion.chunk', moderation=None, obfuscation=None, service_tier=None, system_fingerprint='aeb56401ca74e127821c4f9126dcb669', usage=None) + content = chunk.choices[0].delta.content + if content: # 避免输出 None + print(content, end="", flush=True) \ No newline at end of file diff --git a/w1_d2/llm-api-demo/04_multi_turn_demo.py b/w1_d2/llm-api-demo/04_multi_turn_demo.py new file mode 100644 index 0000000..26cf61b --- /dev/null +++ b/w1_d2/llm-api-demo/04_multi_turn_demo.py @@ -0,0 +1,47 @@ +# 作业: +# 1.完成多轮对话的demo +# 2.完成让用户选择不同模型的调用不同模型的回复demo +# 3.完成一次持久化存储对话 +# 4.完成一次流式输出demo +# 5.完成RAG流程图的绘制 +# 6.选做:调用xiaomi、硅基流动、chatgpt等其他 AI API 服务 +import os +from openai import OpenAI +from dotenv import load_dotenv +load_dotenv() + +client = OpenAI( + api_key=os.getenv("DASHSCOPE_API_KEY"), + base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", +) + +model = "qwen-plus" + +messages = [ + { + "role": "system", + "content": "你是一名聊天高手,幽默,嘲讽用户,自带 emoji表情", + } +] + +while True: + question = input("用户:").strip() + + if question.lower() in {"exit", "quit"}: + print("对话结束") + break + + if not question: + continue + + messages.append({"role": "user", "content": question}) + + response = client.chat.completions.create( + model=model, + messages=messages, + ) + + answer = response.choices[0].message.content + messages.append({"role": "assistant", "content": answer}) + + print(f"AI:{answer}") \ No newline at end of file diff --git a/w1_d2/llm-api-demo/05_deepseek_api_params_demo.py b/w1_d2/llm-api-demo/05_deepseek_api_params_demo.py new file mode 100644 index 0000000..b6313db --- /dev/null +++ b/w1_d2/llm-api-demo/05_deepseek_api_params_demo.py @@ -0,0 +1,272 @@ +# -*- coding: utf-8 -*- +""" +DeepSeek API 参数教学演示脚本 +用法: + python demo.py # 默认跑 stream 示例 + python demo.py 1 # 跑第1个示例(model) + python demo.py 8 # 跑第8个示例(tools) +""" +import os +import sys +import json +from openai import OpenAI +from dotenv import load_dotenv + +load_dotenv() + +client = OpenAI( + api_key=os.environ.get("DEEPSEEK_API_KEY"), + base_url="https://api.deepseek.com", +) + +MODEL = "deepseek-chat" + + +# ---------- 1. model ---------- +def demo_model(): + print("【1. model —— 指定模型】") + response = client.chat.completions.create( + model=MODEL, + messages=[{"role": "user", "content": "你好,请用一句话自我介绍。"}], + ) + + print(response.choices[0].message.content) + print(response.model_dump_json(indent=2)) + + +# ---------- 2. messages ---------- +def demo_messages(): + print("【2. messages —— 系统/用户/历史消息】") + response = client.chat.completions.create( + model=MODEL, + messages=[ + {"role": "system", "content": "你是一个只用文言文回答的助手。"}, + {"role": "user", "content": "什么是API?"}, + {"role": "assistant", "content": "应用程序接口也。"}, + {"role": "user", "content": "那什么是流式输出?"}, + ], + ) + print(response.choices[0].message.content) + + +# ---------- 3. stream ---------- +def demo_stream(): + print("【3. stream —— 流式输出】") + response = client.chat.completions.create( + model=MODEL, + messages=[{"role": "user", "content": "用三句话介绍AI的流式输出的作用。"}], + stream=True, + ) + for chunk in response: + content = chunk.choices[0].delta.content + if content: + print(content, end="", flush=True) + print() # 收尾换行 + + +# ---------- 4. temperature ---------- +def demo_temperature(): + print("【4. temperature —— 随机性】") + prompt = [{"role": "user", "content": "我姓朱,给我小孩起个名字,要有一个凯字,命中缺少需要一个带水的,只给1个。"}] + + cold = client.chat.completions.create( + model=MODEL, messages=prompt, temperature=0.2 + ) + hot = client.chat.completions.create( + model=MODEL, messages=prompt, temperature=2.0 + ) + + print("低温(0.2):", cold.choices[0].message.content.strip()) + print("高温(2.0):", hot.choices[0].message.content.strip()) + + +# ---------- 5. top_p ---------- +def demo_top_p(): + print("【5. top_p —— 候选词概率范围】") + response = client.chat.completions.create( + model=MODEL, + messages=[{"role": "user", "content": "续写:从前有座山,"}], + top_p=0.9, + ) + print(response.choices[0].message.content) + + +# ---------- 6. max_tokens ---------- +def demo_max_tokens(): + print("【6. max_tokens —— 限制输出长度】") + response = client.chat.completions.create( + model=MODEL, + messages=[{"role": "user", "content": "讲一个很长的故事。"}], + max_tokens=50, + ) + print(response.choices[0].message.content) + print("结束原因:", response.choices[0].finish_reason) # 可能是 "length" + + +# ---------- 7. response_format ---------- +def demo_response_format(): + print("【7. response_format —— 结构化输出】") + response = client.chat.completions.create( + model=MODEL, + messages=[ + {"role": "system", "content": "你是一个文本提取助手,输出json格式"}, + {"role": "user", "content": "我叫刘杰,性别男,爱好女,工资null"}, + ], + response_format={"type": "json_object"}, + ) + text = response.choices[0].message.content + print("原始输出:", text) + + # 验证是不是合法 JSON + try: + print(type(text)) + data = json.loads(text) + print("解析成功:", data,type(data)) + except json.JSONDecodeError as e: + print("解析失败:", e) + + +# ---------- 8. tools ---------- +def demo_tools(): + print("【8. tools —— 声明外部工具】") + tools = [ + { + "type": "function", + "function": { + "name": "get_weather", + "description": "查询指定城市的天气", + "parameters": { + "type": "object", + "properties": { + "city": {"type": "string", "description": "城市名"} + }, + "required": ["city"], + }, + }, + }, + { + "type": "function", + "function": { + "name": "get_handsome_boy_score", + "description": "查询学生的帅气程度", + "parameters": { + "type": "object", + "properties": { + "name": {"type": "string", "description": "人名"} + }, + "required": ["name"], + }, + }, + } + ] + + response = client.chat.completions.create( + model=MODEL, + # messages=[{"role": "user", "content": "北京今天天气怎么样?"}], + messages=[{"role": "user", "content": "李毅长的怎么样?"}], + tools=tools, + ) + + msg = response.choices[0].message + if msg.tool_calls: + call = msg.tool_calls[0] + print("要调用的函数:", call.function.name) + print("参数:", call.function.arguments) + else: + print("模型直接回答:", msg.content) + + +# ---------- 9. tool_choice ---------- +def demo_tool_choice(): + print("【9. tool_choice —— 控制工具选择】") + tools = [ + { + "type": "function", + "function": { + "name": "get_weather", + "description": "查询指定城市的天气", + "parameters": { + "type": "object", + "properties": { + "city": {"type": "string", "description": "城市名"} + }, + "required": ["city"], + }, + }, + } + ] + + # 三种模式演示 + modes = [ + ("auto", "auto"), + ("none", "none"), + ("强制get_weather", {"type": "function", "function": {"name": "get_weather"}}), + ] + + for label, choice in modes: + response = client.chat.completions.create( + model=MODEL, + messages=[{"role": "user", "content": "朱凯源是谁?"}], + tools=tools, + tool_choice=choice, + ) + msg = response.choices[0].message + if msg.tool_calls: + print(f"[{label}] 调用工具 -> {msg.tool_calls[0].function.arguments}") + else: + print(f"[{label}] 直接回答 -> {msg.content}") + + +# ---------- 10. extra_body ---------- +def demo_extra_body(): + print("【10. extra_body —— 厂商私有参数】") + # 说明:这里以 deepseek-reasoner 的 thinking 控制为例, + # 具体字段名请以官方文档为准。若模型不支持,可能报错。 + try: + response = client.chat.completions.create( + model="deepseek-reasoner", + messages=[{"role": "user", "content": "9.11 和 9.8 哪个大?"}], + extra_body={"thinking": {"type": "enabled"}}, # "enabled/disabled" + ) + print(response.choices[0].message.content) + print(response.model_dump_json(indent=4)) + except Exception as e: + print("调用失败(可能模型或字段不支持):", e) + + +# ---------- 调度 ---------- +DEMOS = { + 1: demo_model, + 2: demo_messages, + 3: demo_stream, + 4: demo_temperature, + 5: demo_top_p, + 6: demo_max_tokens, + 7: demo_response_format, + 8: demo_tools, + 9: demo_tool_choice, + 10: demo_extra_body, +} + + +def main(): + if len(sys.argv) < 2: + choice = 3 # 默认跑 stream + else: + try: + choice = int(sys.argv[1]) + except ValueError: + print("参数必须是 1~10 的数字") + return + + if choice not in DEMOS: + print(f"没有编号 {choice} 的示例,可用:{sorted(DEMOS.keys())}") + return + + print("=" * 50) + DEMOS[choice]() + print("=" * 50) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/w1_d2/llm-api-demo/06_dashscope.py b/w1_d2/llm-api-demo/06_dashscope.py new file mode 100644 index 0000000..05fb328 --- /dev/null +++ b/w1_d2/llm-api-demo/06_dashscope.py @@ -0,0 +1,17 @@ +import os +import dashscope + +from dotenv import load_dotenv +load_dotenv() +dashscope.base_http_api_url = 'https://maas.qianwenaiapi.com/api/v1' +messages = [ + {'role': 'system', 'content': 'You are a helpful assistant.'}, + {'role': 'user', 'content': 'Who are you?'} +] +response = dashscope.Generation.call( + api_key=os.getenv('DASHSCOPE_API_KEY'), + model='qwen-plus', + messages=messages, + result_format='message' +) +print(response) \ No newline at end of file diff --git a/w1_d2/llm-api-demo/07_fastapi_ds.py b/w1_d2/llm-api-demo/07_fastapi_ds.py new file mode 100644 index 0000000..9e6457e --- /dev/null +++ b/w1_d2/llm-api-demo/07_fastapi_ds.py @@ -0,0 +1,30 @@ +# Please install OpenAI SDK first: `pip3 install openai` +import os +from openai import OpenAI +from dotenv import load_dotenv +load_dotenv() +from fastapi import FastAPI + +app=FastAPI() + +@app.get('/') +def call_llm(user_input,model="deepseek-v4-pro"): + client = OpenAI( + api_key=os.environ.get('DEEPSEEK_API_KEY'), + base_url="https://api.deepseek.com") + + response = client.chat.completions.create( + model=model, + messages=[ + {"role": "system", "content": "You are a helpful assistant"}, + {"role": "user", "content": user_input}, + ], + stream=False, + reasoning_effort="high", + extra_body={"thinking": {"type": "enabled"}} + ) + return response.choices[0].message.content + +if __name__ =='__main__': + import uvicorn + uvicorn.run(app,host='0.0.0.0',port=8001) \ No newline at end of file diff --git a/w1_d2/llm-api-demo/08_fastapi_stream.py b/w1_d2/llm-api-demo/08_fastapi_stream.py new file mode 100644 index 0000000..81e89a0 --- /dev/null +++ b/w1_d2/llm-api-demo/08_fastapi_stream.py @@ -0,0 +1,69 @@ +import os +import json +from fastapi import FastAPI +from fastapi.responses import StreamingResponse +from openai import OpenAI +from dotenv import load_dotenv + +load_dotenv() + +app = FastAPI() + +client = OpenAI( + api_key=os.environ.get("DEEPSEEK_API_KEY"), + base_url="https://api.deepseek.com", +) + + +def stream_generator(user_input: str, model: str): + try: + response = client.chat.completions.create( + model=model, + messages=[ + {"role": "system", "content": "You are a helpful assistant"}, + {"role": "user", "content": user_input}, + ], + stream=True, # 关键:开启流式 + reasoning_effort="high", + extra_body={"thinking": {"type": "enabled"}}, + ) + + for chunk in response: + if not chunk.choices: + continue + + delta = chunk.choices[0].delta + + # DeepSeek 推理内容,如果有就单独发一个 reasoning 事件 + reasoning = getattr(delta, "reasoning_content", None) + if reasoning: + yield f"event: reasoning\ndata: {json.dumps(reasoning, ensure_ascii=False)}\n\n" + + # 最终回答内容 + content = getattr(delta, "content", None) + if content: + yield f"data: {json.dumps(content, ensure_ascii=False)}\n\n" + + yield "event: done\ndata: [DONE]\n\n" + + except Exception as e: + yield f"event: server_error\ndata: {json.dumps(str(e), ensure_ascii=False)}\n\n" + + +@app.get("/") +def call_llm(user_input: str, model: str = "deepseek-v4-pro"): + return StreamingResponse( + stream_generator(user_input, model), + media_type="text/event-stream", + headers={ + "Cache-Control": "no-cache", + "Connection": "keep-alive", + "X-Accel-Buffering": "no", # Nginx 下禁用缓冲 + }, + ) + + +if __name__ == "__main__": + import uvicorn + + uvicorn.run(app, host="0.0.0.0", port=8001) \ No newline at end of file diff --git a/w1_d2/llm-api-demo/requirements.txt b/w1_d2/llm-api-demo/requirements.txt new file mode 100644 index 0000000..f0dd0ae --- /dev/null +++ b/w1_d2/llm-api-demo/requirements.txt @@ -0,0 +1 @@ +openai \ No newline at end of file diff --git a/w1_d2/teach_demo/main.py b/w1_d2/teach_demo/main.py new file mode 100644 index 0000000..6d019f9 --- /dev/null +++ b/w1_d2/teach_demo/main.py @@ -0,0 +1,66 @@ +import os +import json +from fastapi import FastAPI +from fastapi.responses import StreamingResponse +from openai import OpenAI +from dotenv import load_dotenv + +load_dotenv() + +app = FastAPI() + +client = OpenAI( + api_key=os.environ.get("DEEPSEEK_API_KEY"), + base_url="https://api.deepseek.com", +) + + +def stream_generator(user_input: str, model: str): + try: + response = client.chat.completions.create( + model=model, + messages=[ + {"role": "system", "content": "You are a helpful assistant"}, + {"role": "user", "content": user_input}, + ], + stream=True, + # reasoning_effort 与 thinking 参数需按当前 DeepSeek 官方文档核验 + reasoning_effort="high", + extra_body={"thinking": {"type": "enabled"}}, + ) + + for chunk in response: + if not chunk.choices: + continue + delta = chunk.choices[0].delta + + reasoning = getattr(delta, "reasoning_content", None) + if reasoning: + yield f"event: reasoning\ndata: {json.dumps(reasoning, ensure_ascii=False)}\n\n" + + content = getattr(delta, "content", None) + if content: + yield f"data: {json.dumps(content, ensure_ascii=False)}\n\n" + + yield "event: done\ndata: [DONE]\n\n" + + except Exception as e: + yield f"event: server_error\ndata: {json.dumps(str(e), ensure_ascii=False)}\n\n" + + +@app.get("/chat") +def call_llm(user_input: str, model: str = "deepseek-chat"): + return StreamingResponse( + stream_generator(user_input, model), + media_type="text/event-stream", + headers={ + "Cache-Control": "no-cache", + "Connection": "keep-alive", + "X-Accel-Buffering": "no", + }, + ) + + +if __name__ == "__main__": + import uvicorn + uvicorn.run(app, host="0.0.0.0", port=8001) \ No newline at end of file diff --git a/w1_d3/0_yield_review.py b/w1_d3/0_yield_review.py new file mode 100644 index 0000000..64db951 --- /dev/null +++ b/w1_d3/0_yield_review.py @@ -0,0 +1,42 @@ +def my_generator(): + print("开始") + yield 1 + print("继续") + yield 2 + print("结束") + yield 3 + +# g = my_generator() # 注意:此时函数体不会执行! +# print(g,type(g)) # +# # print(print,type(print)) +# print(next(g)) # 输出:开始 \n 1 +# print(next(g)) # 输出:继续 \n 2 +# print(next(g)) # 输出:结束 \n 3 +# # print(next(g)) # 榨干了 + +# g2=my_generator() +# for i in g2: +# print(i) + +# print(len(g2)) # 生成器没有len方法 + + +# import time +# def slow_generator(): +# for i in range(5): +# yield f"第 {i+1} 条数据\n" +# time.sleep(1) # 故意慢一点,方便观察 +# +# g3=slow_generator() +# for i in g3: +# print(i) + + + + + + +import json +d=json.dumps('\n\n') +print(d) +# print('1\n\n2') \ No newline at end of file diff --git a/w1_d3/1_StreamingResponse.py b/w1_d3/1_StreamingResponse.py new file mode 100644 index 0000000..11563a7 --- /dev/null +++ b/w1_d3/1_StreamingResponse.py @@ -0,0 +1,33 @@ +# main.py +import time +from fastapi import FastAPI +from fastapi.responses import StreamingResponse + +app = FastAPI() +def slow_generator(): + for i in range(5): + yield f"第 {i+1} 条数据\n" + time.sleep(1) # 故意慢一点,方便观察 + +@app.get("/non-stream") +def stream(): + return StreamingResponse( + slow_generator(), + media_type="text/plain", # 先用最简单的 text/plain + ) + +@app.get("/stream") +def stream(): + return StreamingResponse( + slow_generator(), + media_type="text/event-stream", # 关键:改成 SSE 类型 + headers={ + "Cache-Control": "no-cache", + "Connection": "keep-alive", + "X-Accel-Buffering": "no", # Nginx 下禁用缓冲 + }, + ) + +if __name__ == '__main__': + import uvicorn + uvicorn.run(app,host='127.0.0.1',port=8001) \ No newline at end of file diff --git a/w1_d3/2_index.html b/w1_d3/2_index.html new file mode 100644 index 0000000..f30ecf0 --- /dev/null +++ b/w1_d3/2_index.html @@ -0,0 +1,146 @@ + + + + + + DeepSeek 流式对话 + + + +

💬 DeepSeek 流式对话

+ +
等待输入…
+ +
+ + +
+ + + + \ No newline at end of file diff --git a/w1_d3/2_sse.py b/w1_d3/2_sse.py new file mode 100644 index 0000000..dbaa85f --- /dev/null +++ b/w1_d3/2_sse.py @@ -0,0 +1,76 @@ +import os +import json +from fastapi import FastAPI +from fastapi.responses import StreamingResponse +from openai import OpenAI +from dotenv import load_dotenv +from starlette.middleware.cors import CORSMiddleware + +load_dotenv() + +app = FastAPI() + +client = OpenAI( + api_key=os.environ.get("DEEPSEEK_API_KEY"), + base_url="https://api.deepseek.com", +) + +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], # 生产环境改成具体域名 + allow_methods=["*"], + allow_headers=["*"], +) + + +def stream_generator(user_input: str, model: str): + try: + response = client.chat.completions.create( + model=model, + messages=[ + {"role": "system", "content": "You are a helpful assistant"}, + {"role": "user", "content": user_input}, + ], + stream=True, + # reasoning_effort 与 thinking 参数需按当前 DeepSeek 官方文档核验 + reasoning_effort="high", + extra_body={"thinking": {"type": "enabled"}}, + ) + + for chunk in response: + if not chunk.choices: + continue + delta = chunk.choices[0].delta + import time + time.sleep(0.5) + + reasoning = getattr(delta, "reasoning_content", None) + if reasoning: + yield f"event: reasoning\ndata: {json.dumps(reasoning, ensure_ascii=False)}\n\n" + + content = getattr(delta, "content", None) + if content: + yield f"event: content\ndata: {json.dumps(content, ensure_ascii=False)}\n\n" + + yield "event: done\ndata: [DONE]\n\n" + + except Exception as e: + yield f"event: server_error\ndata: {json.dumps(str(e), ensure_ascii=False)}\n\n" + + +@app.get("/chat") +def call_llm(user_input: str, model: str = "deepseek-chat"): + return StreamingResponse( + stream_generator(user_input, model), + media_type="text/event-stream", + headers={ + "Cache-Control": "no-cache", + "Connection": "keep-alive", + "X-Accel-Buffering": "no", + }, + ) + + +if __name__ == "__main__": + import uvicorn + uvicorn.run(app, host="0.0.0.0", port=8001) \ No newline at end of file diff --git a/w1_d3/3_toolcalls.py b/w1_d3/3_toolcalls.py new file mode 100644 index 0000000..eab83d0 --- /dev/null +++ b/w1_d3/3_toolcalls.py @@ -0,0 +1,55 @@ +from openai import OpenAI + +client = OpenAI( + api_key='sk-83c3ebe6b1b04bdf93c3c4be9b17736d', + base_url="https://api.deepseek.com", +) + + +def send_messages(messages,model="deepseek-flash"): + response = client.chat.completions.create( + model=model, + messages=messages, + tools=tools + ) + return response.choices[0].message + + +tools = [ + { + "type": "function", + "function": { + "name": "get_weather", + "description": "Get weather of a location, the user should supply a location first.", + "parameters": { + "type": "object", + "properties": { + "location": { + "type": "string", + "description": "The city and state, e.g. San Francisco, CA", + } + }, + "required": ["location"] + }, + } + }, +] + +messages = [{"role": "user", "content": "How's the weather in Hangzhou, Zhejiang?"}] +AI_message = send_messages(messages) +print(AI_message,type(AI_message)) +print(f"User>\t {messages[0]['content']}") +print(f"AI>\t {AI_message.content}") +# AI_message_dict={"role": "assitant", "content": "I'll check the current weather in Hangzhou, Zhejiang for you."} +tool = AI_message.tool_calls[0] +messages.append(AI_message) +# messages.append(AI_message_dict) +# ================================= +# 调用函数返回结果 +tool_result={"role": "tool", "tool_call_id": tool.id, "content": "24℃"} +# ================================= + +messages.append(tool_result) +print(messages) +message = send_messages(messages) +print(f"Model>\t {message.content}") \ No newline at end of file diff --git a/w1_d3/4_toolcalls2.py b/w1_d3/4_toolcalls2.py new file mode 100644 index 0000000..c383bb0 --- /dev/null +++ b/w1_d3/4_toolcalls2.py @@ -0,0 +1,85 @@ +from openai import OpenAI +import os +from dotenv import load_dotenv + +load_dotenv() + +client = OpenAI( + api_key=os.environ.get("DEEPSEEK_API_KEY"), + base_url="https://api.deepseek.com", +) + +# 1. 定义一个获取天气的工具 +tools = [ + { + "type": "function", + "function": { + "name": "get_weather", + "description": "获取指定城市的天气", + "parameters": { + "type": "object", + "properties": { + "location": {"type": "string", "description": "城市名,如杭州"}, + }, + "required": ["location"], + }, + }, + } +] + +# 2. 用户提问 +messages = [{"role": "user", "content": "你好,杭州的天气怎么样"}] + +# 第一次调用模型:模型会返回 tool_calls +response = client.chat.completions.create( + model="deepseek-v4-pro", # 换成你实际可用的模型名,如 deepseek-v4-pro + messages=messages, + tools=tools, # 传入工具列表 +) + +# 获取模型的回复(通常包含 tool_calls) +assistant_msg = response.choices[0].message +print(assistant_msg) +print(assistant_msg.model_dump_json(indent=2)) +print(assistant_msg.content) +print("模型第一次回复:", assistant_msg.content) + + +if assistant_msg.tool_calls: + name=assistant_msg.tool_calls[0].function.name + print(name) + import json + r=json.loads(assistant_msg.tool_calls[0].function.arguments) + print(r) + arg=r.get('location') + print(arg) + tool_id=assistant_msg.tool_calls[0].id + print(tool_id) + + + name2=assistant_msg.tool_calls[1].function.name + print(name) + import json + r2=json.loads(assistant_msg.tool_calls[1].function.arguments) + print(r) + arg2=r.get('location') + print(arg) + tool_id2=assistant_msg.tool_calls[1].id + print(tool_id) + +def get_weather(location: str) -> str: + return f"{location}的天气为 880℃" + +tools_map={'get_weather':get_weather} +tools_result=tools_map.get(name)(**r) +tools_result=tools_map.get(name2)(**r2) +print(tools_result) +messages.append(assistant_msg) +messages.append({'role':"tool","tool_call_id": tool_id,'content':tools_result}) + +response = client.chat.completions.create( + model="deepseek-v4-pro", # 换成你实际可用的模型名,如 deepseek-v4-pro + messages=messages, + tools=tools, # 传入工具列表 +) +print("模型第二次回复:", response.choices[0].message.content) diff --git a/w1_d3/5_toolcalls3.py b/w1_d3/5_toolcalls3.py new file mode 100644 index 0000000..19f84ca --- /dev/null +++ b/w1_d3/5_toolcalls3.py @@ -0,0 +1,86 @@ +from openai import OpenAI +import os +from dotenv import load_dotenv + +load_dotenv() + +client = OpenAI( + api_key=os.environ.get("DEEPSEEK_API_KEY"), + base_url="https://api.deepseek.com", +) + +# 1. 定义一个获取天气的工具 +tools = [ + { + "type": "function", + "function": { + "name": "get_weather", + "description": "获取指定城市的天气", + "parameters": { + "type": "object", + "properties": { + "location": {"type": "string", "description": "城市名,如杭州"}, + }, + "required": ["location"], + }, + }, + } +] + +# 2. 用户提问 +messages = [{"role": "user", "content": "你好,杭州和深圳的天气怎么样"}] + +# 第一次调用模型:模型会返回 tool_calls +response = client.chat.completions.create( + model="deepseek-v4-pro", # 换成你实际可用的模型名,如 deepseek-v4-pro + messages=messages, + tools=tools, # 传入工具列表 +) + +# 获取模型的回复(通常包含 tool_calls) +assistant_msg = response.choices[0].message +print(assistant_msg) +print(assistant_msg.model_dump_json(indent=2)) +print(assistant_msg.content) +print("模型第一次回复:", assistant_msg.content) + + +if assistant_msg.tool_calls: + name=assistant_msg.tool_calls[0].function.name + print(name) + import json + r=json.loads(assistant_msg.tool_calls[0].function.arguments) + print(r) + arg=r.get('location') + print(arg) + tool_id=assistant_msg.tool_calls[0].id + print(tool_id) + + + name2=assistant_msg.tool_calls[1].function.name + print(name2) + import json + r2=json.loads(assistant_msg.tool_calls[1].function.arguments) + print(r2) + arg2=r2.get('location') + print(arg2) + tool_id2=assistant_msg.tool_calls[1].id + print(tool_id2) + +def get_weather(location: str) -> str: + return f"{location}的天气为 880℃" + +tools_map={'get_weather':get_weather} +tools_result=tools_map.get(name)(**r) +tools_result2=tools_map.get(name2)(**r2) +print(tools_result) +messages.append(assistant_msg) +messages.append({'role':"tool","tool_call_id": tool_id,'content':tools_result}) +messages.append({'role':"tool","tool_call_id": tool_id2,'content':tools_result2}) + +response = client.chat.completions.create( + model="deepseek-v4-pro", # 换成你实际可用的模型名,如 deepseek-v4-pro + messages=messages, + tools=tools, # 传入工具列表 +) +print("模型第二次回复:", response.choices[0].message.content) diff --git a/w1_d3/6_toolcalls4.py b/w1_d3/6_toolcalls4.py new file mode 100644 index 0000000..e57968a --- /dev/null +++ b/w1_d3/6_toolcalls4.py @@ -0,0 +1,85 @@ +from openai import OpenAI +import os +from dotenv import load_dotenv + +load_dotenv() + +client = OpenAI( + api_key=os.environ.get("DEEPSEEK_API_KEY"), + base_url="https://api.deepseek.com", +) + +# 1. 定义一个获取天气的工具 +tools = [ + { + "type": "function", + "function": { + "name": "get_weather", + "description": "获取指定城市的天气", + "parameters": { + "type": "object", + "properties": { + "location": {"type": "string", "description": "城市名,如杭州"}, + }, + "required": ["location"], + }, + }, + } +] + +# 2. 用户提问 +messages = [{"role": "user", "content": "你好,杭州和深圳和北京的天气怎么样"}] + +# 第一次调用模型:模型会返回 tool_calls +response = client.chat.completions.create( + model="deepseek-v4-pro", # 换成你实际可用的模型名,如 deepseek-v4-pro + messages=messages, + tools=tools, # 传入工具列表 +) + +# 获取模型的回复(通常包含 tool_calls) +assistant_msg = response.choices[0].message +print(assistant_msg) +print(assistant_msg.model_dump_json(indent=2)) +print(assistant_msg.content) +print("模型第一次回复:", assistant_msg.content) + +def get_weather(location: str) -> str: + return f"{location}的天气为 8℃" +tools_map={'get_weather':get_weather} +messages.append(assistant_msg) +if assistant_msg.tool_calls: + # for i in range(len(assistant_msg.tool_calls)): + # name=assistant_msg.tool_calls[i].function.name + # print(name) + # import json + # r=json.loads(assistant_msg.tool_calls[i].function.arguments) + # print(r) + # arg=r.get('location') + # print(arg) + # tool_id=assistant_msg.tool_calls[i].id + # print(tool_id) + # tools_result = tools_map.get(name)(**r) + # messages.append({'role': "tool", "tool_call_id": tool_id, 'content': tools_result}) + + for tool_call in assistant_msg.tool_calls: + name=tool_call.function.name + print(name) + import json + r=json.loads(tool_call.function.arguments) + print(r) + arg=r.get('location') + print(arg) + tool_id=tool_call.id + print(tool_id) + tools_result = tools_map.get(name)(**r) + messages.append({'role': "tool", "tool_call_id": tool_id, 'content': tools_result}) + + + +response = client.chat.completions.create( + model="deepseek-v4-pro", # 换成你实际可用的模型名,如 deepseek-v4-pro + messages=messages, + tools=tools, # 传入工具列表 +) +print("模型第二次回复:", response.choices[0].message.content) diff --git a/w1_d3/7_toolcalls5.py b/w1_d3/7_toolcalls5.py new file mode 100644 index 0000000..e19baaf --- /dev/null +++ b/w1_d3/7_toolcalls5.py @@ -0,0 +1,101 @@ +from openai import OpenAI +import os +import json +from dotenv import load_dotenv + +load_dotenv() + +client = OpenAI( + api_key=os.environ.get("DEEPSEEK_API_KEY"), + base_url="https://api.deepseek.com", +) + +# 1. 定义一个获取天气的工具 +tools = [ + { + "type": "function", + "function": { + "name": "get_weather", + "description": "获取指定城市的天气", + "parameters": { + "type": "object", + "properties": { + "location": {"type": "string", "description": "城市名,如杭州"}, + }, + "required": ["location"], + }, + }, + } +] + +# 工具实现 +def get_weather(location: str) -> str: + return f"{location}的天气为 8℃" + +tools_map = {'get_weather': get_weather} + +# ====================== 【新增:工具执行函数】====================== +def execute_tool_calls(tool_calls, tools_map: dict) -> list: + """ + 执行模型返回的tool_calls,返回tool角色消息列表,用于追加到messages + :param tool_calls: response.choices[0].message.tool_calls + :param tools_map: 工具名字 -> 函数 的映射字典 + :return: list[dict] tool消息数组 + """ + tool_messages = [] + for tool_call in tool_calls: + func_name = tool_call.function.name + print(f"调用工具:{func_name}") + # 解析参数 + args = json.loads(tool_call.function.arguments) + print(f"参数:{args}") + # 获取工具函数并执行 + func = tools_map[func_name] + result = func(**args) + # 组装tool消息 + tool_msg = { + "role": "tool", + "tool_call_id": tool_call.id, + "content": result + } + tool_messages.append(tool_msg) + return tool_messages +# ================================================================== + +# 2. 用户提问 +messages = [{"role": "user", "content": "你好,杭州和深圳和北京的天气怎么样"}] + +# 第一次调用模型:模型会返回 tool_calls +response = client.chat.completions.create( + model="deepseek-v4-pro", + messages=messages, + tools=tools, +) + +# 获取模型的回复 +assistant_msg = response.choices[0].message +print(assistant_msg) +print(assistant_msg.model_dump_json(indent=2)) +print(assistant_msg.content) +print("模型第一次回复:", assistant_msg.content) + +# 把assistant消息放进对话上下文 +messages.append(assistant_msg) + +# 如果存在工具调用,执行工具 +if assistant_msg.tool_calls: + # 调用封装好的函数,拿到tool消息列表 + tool_msg_list = execute_tool_calls(assistant_msg.tool_calls, tools_map) + # 批量追加tool结果到对话 + messages.extend(tool_msg_list) + + # 第二次请求模型,传入工具返回结果 + response = client.chat.completions.create( + model="deepseek-v4-pro", + messages=messages, + tools=tools, + ) + print("模型第二次回复:", response.choices[0].message.content) + +# 思考题:如何让你的程序 实现自己判断是否还需要再次请求大模型的调用 +# 参考思路:当大模型不再需要工具调用的时候就结束大模型请求 \ No newline at end of file diff --git a/w1_d3/8_toolcalls6.py b/w1_d3/8_toolcalls6.py new file mode 100644 index 0000000..74adfdb --- /dev/null +++ b/w1_d3/8_toolcalls6.py @@ -0,0 +1,78 @@ +from openai import OpenAI +import os +import json +import requests +from dotenv import load_dotenv + +load_dotenv() + +client = OpenAI( + api_key=os.environ.get("DEEPSEEK_API_KEY"), + base_url="https://api.deepseek.com", +) + +# 1. 定义一个获取天气的工具 +tools = [ + { + "type": "function", + "function": { + "name": "get_weather", + "description": "获取指定城市的天气", + "parameters": { + "type": "object", + "properties": { + "location": {"type": "string", "description": "城市名,如杭州"}, + }, + "required": ["location"], + }, + }, + } +] + +# 2. 用户提问 +messages = [{"role": "user", "content": "杭州天气怎么样?"}] + +# 第一次调用模型:模型会返回 tool_calls +response = client.chat.completions.create( + model="deepseek-chat", # 换成你实际可用的模型名,如 deepseek-v4-pro + messages=messages, + tools=tools, # 传入工具列表 +) + +# 获取模型的回复(通常包含 tool_calls) +assistant_msg = response.choices[0].message +print("模型第一次回复:", assistant_msg) + +# 3. 将模型的回复加入对话历史(必须转换为字典,否则 API 会报错) +messages.append(assistant_msg.model_dump()) # 或 dict(assistant_msg) + + +def get_weather(location: str) -> str: + """调用高德地图天气API查询真实天气""" + url = "https://restapi.amap.com/v3/weather/weatherInfo" + params = { + "key": os.getenv("GAODE_API_KEY"), + "city": location, + "extensions": "all", + "output": "json" + } + response = requests.get(url, params=params) + data = response.json() + return json.dumps(data, ensure_ascii=False, indent=2) + +# 4. 执行工具调用 +if assistant_msg.tool_calls: + tool_call = assistant_msg.tool_calls[0] + args = json.loads(tool_call.function.arguments) + result = get_weather(args["location"]) + tool_result = {"role": "tool", "tool_call_id": tool_call.id, "content": result} + messages.append(tool_result) + +# 5. 第二次调用模型,将工具返回的结果发给模型,生成最终回答 +final_response = client.chat.completions.create( + model="deepseek-chat", + messages=messages, + tools=tools, +) +final_answer = final_response.choices[0].message.content +print("最终回答:", final_answer) \ No newline at end of file diff --git a/w1_d3/9.py b/w1_d3/9.py new file mode 100644 index 0000000..6924aed --- /dev/null +++ b/w1_d3/9.py @@ -0,0 +1,125 @@ +import smtplib +from email.mime.text import MIMEText +from openai import OpenAI +import os +import json +from dotenv import load_dotenv + +load_dotenv() + +client = OpenAI( + api_key=os.environ.get("DEEPSEEK_API_KEY"), + base_url="https://api.deepseek.com", +) + +# 1. 定义工具描述 +tools = [ + { + "type": "function", + "function": { + "name": "get_weather", + "description": "获取指定城市的天气", + "parameters": { + "type": "object", + "properties": { + "location": {"type": "string", "description": "城市名,如杭州"}, + }, + "required": ["location"], + }, + }, + }, + { + "type": "function", + "function": { + "name": "send_email_qq", + "description": "使用QQ邮箱SMTP发送纯文本邮件", + "parameters": { + "type": "object", + "properties": { + "text": {"type": "string", "description": "邮件正文内容"}, + "subject": {"type": "string", "description": "邮件主题"}, + }, + "required": ["text", "subject"], + }, + }, + } +] + +# 工具实现 +def get_weather(location: str) -> str: + return f"{location}的天气为 8℃" + +def send_email_qq(text, subject): + mail_host = "smtp.qq.com" + mail_port = 465 + mail_user = "wolin105@qq.com" + mail_pass = "pingnllumhbzbceg" # QQ邮箱授权码 + sender = mail_user + receivers = "jzk.jie@qq.com" + + message = MIMEText(text, 'plain', 'utf-8') + message["From"] = sender + message["To"] = receivers + message["Subject"] = subject + + try: + smtp = smtplib.SMTP_SSL(mail_host, mail_port) + smtp.login(mail_user, mail_pass) + smtp.sendmail(sender, [receivers], message.as_string()) + smtp.quit() + print("邮件发送成功") + return "邮件发送成功" + except Exception as e: + err_msg = f"邮件发送失败: {e}" + print(err_msg) + return err_msg + +tools_map = {'get_weather': get_weather, 'send_email_qq': send_email_qq} + +def execute_tool_calls(tool_calls, tools_map: dict) -> list: + """执行模型返回的tool_calls,返回tool角色消息字典列表""" + tool_messages = [] + for tool_call in tool_calls: + func_name = tool_call.function.name + print(f"\n调用工具:{func_name}") + args = json.loads(tool_call.function.arguments) + print(f"参数:{args}") + func = tools_map[func_name] + result = func(**args) + tool_msg = { + "role": "tool", + "tool_call_id": tool_call.id, + "content": result + } + tool_messages.append(tool_msg) + return tool_messages + + +if __name__ == "__main__": + messages = [ + {"role": "user", "content": "帮我发一个邮件,邮件内容是描述今天学了functioncalling的心得,主题是学习心得,只输出文本即可"} + ] + + # ✅ while循环自动多轮工具调用,直到模型不再返回tool_calls + while True: + response = client.chat.completions.create( + model="deepseek-v4-pro", + messages=messages, + tools=tools, + ) + assistant_msg = response.choices[0].message + print("\n=====模型返回assistant消息对象=====") + print(assistant_msg) + + # ✅ 关键修复:转成字典,再追加到messages,不能直接append对象! + messages.append(assistant_msg.model_dump()) + + # 判断是否还有工具调用 + if not assistant_msg.tool_calls: + # 没有工具调用,拿到最终回答,退出循环 + print("\n模型最终回复:", assistant_msg.content) + break + + # 执行工具,拿到tool消息 + tool_msg_list = execute_tool_calls(assistant_msg.tool_calls, tools_map) + messages.extend(tool_msg_list) diff --git a/w1_d3/99_requests_deepseek_api.py b/w1_d3/99_requests_deepseek_api.py new file mode 100644 index 0000000..9aac26d --- /dev/null +++ b/w1_d3/99_requests_deepseek_api.py @@ -0,0 +1,38 @@ +import requests +import json + +url = "https://api.deepseek.com/responses" + +payload = json.dumps({ + "model": "deepseek-flash", + "input": "你好", + "instructions": "string", + "reasoning": { + "effort": "none" + }, + "max_output_tokens": 4096, + "stream": False, + "temperature": 1, + "top_p": 1, + "text": { + "format": { + "type": "text", + "name": "string", + "schema": {} + } + }, + "tools": None, + "tool_choice": "none", + "top_logprobs": None, + "user": "string" +}) +print(payload,type(payload)) +headers = { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + 'Authorization': 'Bearer sk-83c3ebe6b1b04bdf93c3c4be9b17736d' +} + +response = requests.request("POST", url, headers=headers, data=payload) + +print(response.text) \ No newline at end of file diff --git a/w1_d3/base_func.py b/w1_d3/base_func.py new file mode 100644 index 0000000..77c7873 --- /dev/null +++ b/w1_d3/base_func.py @@ -0,0 +1,38 @@ +# +# def get_weather(location): +# return f"{location}的天气为 30℃" +# +# +# r=get_weather('sz') +# print(r) +# +# # 字符串 映射 对象 +# d2={'get_weather':get_weather} +# print(d2['get_weather']) +# print(d2.get('get_weather')) +# +# # 方式1: 使用中间变量 +# f=d2.get('get_weather') +# r=f('sz') +# print(r) +# +# # 方式2: 直接一步到位 +# r=d2.get('get_weather')('sz') +# print(r) +# +# args={'location':'sz'} +# print(get_weather(**args)) + + +# def get_weather(location,province): +# return f"{province}{location}的天气为 30℃" +# +# ars={'location':'sz','province':'gd'} +# print(get_weather(**ars)) +# print(get_weather(location='sz',province='gd')) + +MES=['user','ai'] +L=['tool1','tool2','tool3'] +# MES.append(L) +MES.extend(L) +print(MES) \ No newline at end of file diff --git a/w1_d3/gode_get_weather.py b/w1_d3/gode_get_weather.py new file mode 100644 index 0000000..3fcdaab --- /dev/null +++ b/w1_d3/gode_get_weather.py @@ -0,0 +1,35 @@ +import requests, json +import os +from dotenv import load_dotenv + +load_dotenv(override=True) + +# key='8e3ff61eecb810dcce3bc5377b739222' +def get_weather(city): + """ + 查询高德地图天气函数 + :param city_adcode: 必要参数,字符串类型,城市的 adcode 编码,例如北京市是 110000 + :return:高德地图天气 API 查询结果,JSON 格式字符串,包含实况/预报天气信息 + """ + # Step 1. 高德地图天气 API 地址 + url = "https://restapi.amap.com/v3/weather/weatherInfo" + + # Step 2. 设置请求参数(完全按高德官方要求) + params = { + "key": os.getenv("GAODE_API_KEY"), # 高德 Web 服务 API Key + # "key": key, # 高德 Web 服务 API Key + "city": city, # 城市 + "extensions": "all", # base=实况天气,all=预报天气 + "output": "json" # 返回 JSON 格式 + } + + # Step 3. 发送 GET 请求 + response = requests.get(url, params=params) + + # Step 4. 解析响应并返回 JSON 字符串 + data = response.json() + return json.dumps(data, ensure_ascii=False, indent=2) + +print(get_weather("深圳")) # 查询深圳天气 +print(get_weather("北京")) # 查询北京天气 +print(get_weather("乌鲁木齐")) # 查询乌鲁木齐天气 \ No newline at end of file diff --git a/w1_d3/object_property.py b/w1_d3/object_property.py new file mode 100644 index 0000000..8d0e2ca --- /dev/null +++ b/w1_d3/object_property.py @@ -0,0 +1,57 @@ +from dataclasses import dataclass + +# 最内层:人 +@dataclass +class Person: + name: str + age: int + job: str + +# 城市:里面包含 Person 对象 +@dataclass +class City: + city_name: str + people: Person # 成员是 Person 对象 + +# 省份:里面包含城市列表 +@dataclass +class Province: + province_name: str + city_list: list[City] + +# 最外层顶级对象 Country,对标 LLM 的 res 返回对象 +@dataclass +class Country: + country_name: str + province_list: list[Province] +# 先造最内层的人 +p = Person(name="张三", age=20, job="程序员") + +# 造城市,把人塞进去 +hz = City(city_name="杭州市", people=p) + +# 造省份,把城市放进列表 +zj = Province(province_name="浙江省", city_list=[hz]) + +# 顶级大对象:整个中国,对标 res +china = Country( + country_name="中国", + province_list=[zj] +) +# 直接打印顶级对象 dataclass 友好一点,但依然不能直接拿内层数据 +print(china) + +# 逐层拆解,和 LLM 取值一一对应 +# 1.取省份列表 +provinces = china.province_list +# 2.取第0个省份 +zhejiang = provinces[0] +# 3.取该省第0个城市 +hangzhou = zhejiang.city_list[0] +# 4.拿到人对象,取 name +person_name = hangzhou.people.name + +print(person_name) # 张三 +print(hangzhou.people.age) # 20 +result = china.province_list[0].city_list[0].people.name +print(result) diff --git a/w1_d3/property.py b/w1_d3/property.py new file mode 100644 index 0000000..f009cc1 --- /dev/null +++ b/w1_d3/property.py @@ -0,0 +1,35 @@ +from functools import cached_property + +class Person: + def __init__(self, height, bmi): + self.height = height + self.bmi = bmi + + @cached_property + def weight(self): + print("====正在执行复杂计算====") + return self.height ** 2 * self.bmi + + +p = Person(1.75, 22) + +# 第一次访问:执行函数,打印日志,保存结果 +print(p.weight) +# 第二次访问:直接读缓存!不会打印日志,不重新计算 +print(p.weight) +print(p.weight) + + +class Person2: + def __init__(self, height, bmi): + self.height = height + self.bmi = bmi + + @property + def weight(self): + print("====每次都重新计算====") + return self.height ** 2 * self.bmi + +p2 = Person2(1.75,22) +print(p2.weight) +print(p2.weight) diff --git a/w1_d3/send_email_qq.py b/w1_d3/send_email_qq.py new file mode 100644 index 0000000..20e5f17 --- /dev/null +++ b/w1_d3/send_email_qq.py @@ -0,0 +1,54 @@ +import smtplib +from email.mime.text import MIMEText + + + +# Please install OpenAI SDK first: `pip3 install openai` +import os +from openai import OpenAI +from dotenv import load_dotenv +load_dotenv() +client = OpenAI( + api_key=os.environ.get('DEEPSEEK_API_KEY'), + base_url="https://api.deepseek.com") + +response = client.chat.completions.create( + model="deepseek-v4-pro", + messages=[ + {"role": "user", "content": "帮我写三句幽默笑话,以国伟不要开头"}, + ], + stream=False, + reasoning_effort="high", + extra_body={"thinking": {"type": "enabled"}} +) + + +text=response.choices[0].message.content +print(text) + + +def send_email_qq(text,subject): + mail_host = "smtp.qq.com" # 官方指定 + mail_port = 465 + mail_user = "wolin105@qq.com" + mail_pass = "pingnllumhbzbceg" # 注意:不是登录密码 + sender = mail_user + receivers = "1426833948@qq.com" + + + + message = MIMEText(text, 'plain', 'utf-8') + message["From"] = sender + message["To"] = receivers + message["Subject"] = subject + + try: + smtp = smtplib.SMTP_SSL(mail_host, mail_port) + smtp.login(mail_user, mail_pass) + smtp.sendmail(sender, [receivers], message.as_string()) + smtp.quit() + print("邮件发送成功") + except Exception as e: + print(f"邮件发送失败: {e}") + +send_email_qq(text,'笑话') \ No newline at end of file diff --git a/zky.png b/zky.png new file mode 100644 index 0000000..e6e6536 Binary files /dev/null and b/zky.png differ