[{"content":"Best practices Strong first use : Context and Prompts Clear prompting makes results more reliable A good default is to include four things in your prompt:\nGoal: What are you trying to change or build? Context: Which files, folders, docs, examples, or errors matter for this task? You can @ mention certain files as context. Constraints: What standards, architecture, safety requirements, or conventions should Codex follow? Done when: What should be true before the task is complete, such as tests passing, behavior changing, or a bug no longer reproducing? it is similar to Claude Code Best practices\nPlan first for difficult tasks A few approaches work well: Use Plan mode:/plan or Shift + Tab Ask Codex to interview you: when your idea is ambiguous Use a PLANS.md template: use PLANS.md\nMake guidance reusable with AGENTS.md Think of AGENTS.md as an open-format README for agents.\nA good AGENTS.md covers:\nrepo layout and important directories How to run the project Build, test, and lint commands Engineering conventions and PR expectations Constraints and do-not rules What done means and how to verify work I think information above is very important!!!! /init to scaffold a starter AGENTS.md Keep it practical. A short, accurate AGENTS.md is more useful than a long file full of vague rules. If AGENTS.md starts getting too large, keep the main file concise and reference task-specific markdown files for things like planning, code review, or architecture.\nConfigure Codex for consistency A good starting pattern is:\nKeep personal defaults in ~/.codex/config.toml (Settings \u0026gt; Configuration \u0026gt; Open config.toml in the ChatGPT desktop app) Keep repo-specific behavior in .codex/config.toml Use command-line overrides only for one-off situations (if you use the CLI) Improve reliability with testing and review Don\u0026rsquo;t stop at asking Codex to make a change. Ask it to create tests when needed, run the relevant checks, confirm the result, and review the work before you accept it. Codex can do this loop for you, but only if it knows what “good” looks like. That guidance can come from either the prompt or AGENTS.md. /review gives you a few ways to review code\nIf you and your team have a code_review.md file and reference it from AGENTS.md, Codex can follow that guidance during review as well.\nUse MCPs for external context Use MCP when:\nThe needed context lives outside the repo The data changes frequently You want Codex to use a tool rather than rely on pasted instructions You need a repeatable integration across users or projects codex mcp add command in the CLI to add your custom servers with a name, URL, and other details.\nTurn repeatable work into skills Use a skill to package the instructions in a SKILL.md file\nWhat a skill.md file should include: 1.Keep each skill scoped to one job 2.Start with 2 to 3 concrete use cases 3.define clear inputs and outputs 4.write the description so it says what the skill does and when to use it 5.Include the kinds of trigger phrases a user would actually say\nThe often situation:\nLog triage Release note drafting PR review against a checklist Migration planning Telemetry or incident summaries Standard debugging flows The $skill-creator skill is the best place to start to scaffold the first version of a skill.\nUse scheduled tasks for repeated work Good candidates include:\nSummarizing recent commits Scanning for likely bugs Drafting release notes Checking CI failures Producing standup summaries Running repeatable analysis workflows on a schedule Organize long-running chats The ChatGPT desktop app lets you pin chats and create worktrees. If you use the CLI, these slash commands are especially useful:\n/experimental to toggle experimental features and add to your config.toml /resume to resume a saved chat /fork to create a new chat while preserving the original transcript /compact when the chat is getting long and you want a summarized version of earlier context. Codex also compacts chats automatically /agent when you are running parallel agents and want to switch between the active agent thread /theme to choose a syntax highlighting theme /apps to use ChatGPT apps directly in Codex /status to inspect the current session state Common mistakes A few common mistakes to avoid when first using Codex:\nOverloading the prompt with durable rules instead of moving them into AGENTS.md or a skill Not letting the agent see its work by not giving details on how to best run build and test commands Skipping planning on multi-step and complex tasks Giving Codex full permission to your computer before you understand the workflow Running live tasks on the same files without using Git worktrees Scheduling a recurring task before it\u0026rsquo;s reliable manually Treating Codex like something you have to watch step by step instead of using it in parallel with your own work Using one chat for an entire project instead of one chat per coherent outcome. This leads to bloated context and worse results over time ","permalink":"https://tuanzi-wow.cn/vibe-coding/vibe-coding-best-practices/","summary":"\u003ch2 id=\"best-practices\"\u003eBest practices\u003c/h2\u003e\n\u003ch2 id=\"strong-first-use--context-and-prompts\"\u003eStrong first use : Context and Prompts\u003c/h2\u003e\n\u003cp\u003eClear prompting makes results more reliable\nA good default is to include four things in your prompt:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eGoal:\u003c/strong\u003e What are you trying to change or build?\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eContext:\u003c/strong\u003e Which files, folders, docs, examples, or errors matter for this task? You can @ mention certain files as context.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eConstraints:\u003c/strong\u003e What standards, architecture, safety requirements, or conventions should Codex follow?\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eDone when:\u003c/strong\u003e What should be true before the task is complete, such as tests passing, behavior changing, or a bug no longer reproducing?\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eit is similar to Claude Code Best practices\u003c/p\u003e","title":"Vibe Coding 最佳实践"},{"content":"Custom instructions with AGENTS.md agents.md is read before doing any work\nHow codex discovers guidance: The instruction chain:\n1.Global scope: in your Codex directory(default~/.codex): u can also use AGENTS.override.md,but only the first non-empty AGENTS.md is read\n2.Project scope: At the project root in each directory along the path,it checks for AGENTS.override.md ,then AGENTS.md,then any fallback names in project_doc_fallback_filenames. Codex includes at most one file per directory\n3.Merge order: The later ,the higher : because they appear later in the prompt\nCreate global guidance: 1.make sure the directory exist: 2.create with reusable preference 3.run codex anywhere to confirm it loads the file\nif you want a temporary global ,use AGENTS.override.md\nLayer project instructions: 1.In your repository root, add an AGENTS.md that covers basic setup 2.Add overrides in nested directories when specific teams need different rules. For example, inside services/payments/ create AGENTS.override.md 3.Start Codex from the payments directory:\nAdd code reviews rules: For Codex code review in GitHub, add a ## Code Review Rules section to the AGENTS.md closest to the code the rules govern. Put repository-wide checks at the root and service-specific checks in a nested file.\nCustomize fallback filenames: if u use a customize filename,add it to the fallback list then Codex can really read it as a instructions file: 1.edit your Codex configuration\n1 2 3 # ~/.codex/config.toml project_doc_fallback_filenames = [\u0026#34;TEAM_GUIDE.md\u0026#34;, \u0026#34;.agents.md\u0026#34;] project_doc_max_bytes = 65536 2.Restart Codex or run a new command to update configuration loads:\n","permalink":"https://tuanzi-wow.cn/codex/codex-agents-instructions/","summary":"\u003ch2 id=\"custom-instructions-with-agentsmd\"\u003eCustom instructions with AGENTS.md\u003c/h2\u003e\n\u003cp\u003eagents.md is read before doing any work\u003c/p\u003e\n\u003ch2 id=\"how-codex-discovers-guidance\"\u003eHow codex discovers guidance:\u003c/h2\u003e\n\u003cp\u003eThe instruction chain:\u003c/p\u003e\n\u003ch3 id=\"1global-scope\"\u003e1.Global scope:\u003c/h3\u003e\n\u003cp\u003ein your Codex directory(default~/.codex):\nu can also use AGENTS.override.md,but only the first non-empty AGENTS.md is read\u003c/p\u003e\n\u003ch3 id=\"2project-scope\"\u003e2.Project scope:\u003c/h3\u003e\n\u003cp\u003eAt the project root\nin each directory along the path,it checks for AGENTS.override.md ,then AGENTS.md,then any fallback names in \u003ccode\u003eproject_doc_fallback_filenames\u003c/code\u003e.\nCodex includes at most one file per directory\u003c/p\u003e\n\u003ch3 id=\"3merge-order\"\u003e3.Merge order:\u003c/h3\u003e\n\u003cp\u003eThe later ,the higher : because they appear later in the prompt\u003c/p\u003e","title":"AGENTS.md 自定义指令"},{"content":"1. Context Windows: 上下文窗口、Token 限制、Context Rot、长上下文管理 https://platform.claude.com/docs/en/build-with-claude/context-windows\n↓\n2. Context Engineering in Agents: Agent 上下文工程、Model Context、Tool Context、生命周期上下文、Summarization、Memory https://docs.langchain.com/oss/python/langchain/context-engineering\n↓\n3. Compaction: 对话历史压缩、自动总结、长时间 Agent 的上下文压缩机制 https://platform.claude.com/docs/en/build-with-claude/compaction\n↓\n4. Context Editing: Tool Result 清理、Thinking Block 清理、Context Compaction、Token Threshold https://platform.claude.com/docs/en/build-with-claude/context-editing\n↓\n5. Context Compaction: Context Compaction 的工程实现、Token-based Compaction、Sliding-window Compaction、Overlap https://adk.dev/context/compaction/\n↓\n6. Deep Agents — Memory: Short-term Memory、Long-term Memory、跨 Session 的持久化记忆 https://docs.langchain.com/oss/python/deepagents/memory\n↓\n7. Context Engineering in Deep Agents: Context Compression、Offloading、Filesystem、Subagent、Context Isolation、Memory https://docs.langchain.com/oss/python/deepagents/context-engineering\n↓\n8. Context Management for Deep Agents: 长时间 Agent 的 Context Management、Compression、Offloading、Subagents、Filesystem https://www.langchain.com/blog/context-management-for-deepagents\n↓\n9. Context Engineering Concepts: Context Engineering、Skills、Agents、Subagents、Context Hub、Agent 配置 https://docs.langchain.com/langsmith/context-engineering-concepts\n","permalink":"https://tuanzi-wow.cn/%E4%B8%8A%E4%B8%8B%E6%96%87%E5%B7%A5%E7%A8%8B/context-engineering-overview/","summary":"\u003ch2 id=\"1-context-windows-上下文窗口token-限制context-rot长上下文管理\"\u003e1. Context Windows: 上下文窗口、Token 限制、Context Rot、长上下文管理\u003c/h2\u003e\n\u003cp\u003e\u003ca href=\"https://platform.claude.com/docs/en/build-with-claude/context-windows?utm_source=chatgpt.com\"\u003ehttps://platform.claude.com/docs/en/build-with-claude/context-windows\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e↓\u003c/p\u003e\n\u003ch3 id=\"2-context-engineering-in-agents-agent-上下文工程model-contexttool-context生命周期上下文summarizationmemory\"\u003e2. Context Engineering in Agents: Agent 上下文工程、Model Context、Tool Context、生命周期上下文、Summarization、Memory\u003c/h3\u003e\n\u003cp\u003e\u003ca href=\"https://docs.langchain.com/oss/python/langchain/context-engineering?utm_source=chatgpt.com\"\u003ehttps://docs.langchain.com/oss/python/langchain/context-engineering\u003c/a\u003e\u003c/p\u003e","title":"Context Engineering · Overview"},{"content":"//我说句实在话,这个菜鸟教程写的好简单也挺烂的,不过很容易看懂吧,还真是菜鸟教程,而且代码又臭又长\nFunction Calling(工具调用) 是LLM能够调用外部工具的核心机制,允许模型决定什么时候调用工具,调用什么工具,以及传递什么参数\nFunction Calling的工作原理: 其基本流程如下:\n核心概念: 工具定义:描述一个工具的功能,参数和返回值 工具选择:LLM根据用户问题选择合适的工具 参数提取:LLM从问题中提取工具所需要的参数 结果处理:将工具执行结果整合到最终回答中\n实例说明: LLM 识别出需要调用天气查询工具 从问题中提取参数：city=\u0026quot;北京\u0026quot;, date=\u0026quot;今天\u0026quot; 调用天气 API 获取数据 将天气数据整合成友好的回答返回给用户 工具定义和描述编写 好的工具定义应该描述: what:工具的功能 when:什么时候调用它 how:需要的参数;参数的格式;返回值\n工具定义的结构: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 # 工具定义示例结构 weather_tool = { \u0026#34;name\u0026#34;: \u0026#34;get_weather\u0026#34;, # 工具名称 \u0026#34;description\u0026#34;: \u0026#34;获取指定城市的天气信息\u0026#34;, # 工具描述 \u0026#34;parameters\u0026#34;: { # 参数定义 \u0026#34;type\u0026#34;: \u0026#34;object\u0026#34;, \u0026#34;properties\u0026#34;: { \u0026#34;city\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;string\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;城市名称，如\u0026#39;北京\u0026#39;、\u0026#39;上海\u0026#39;\u0026#34; }, \u0026#34;date\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;string\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;日期，格式\u0026#39;YYYY-MM-DD\u0026#39;，或\u0026#39;今天\u0026#39;、\u0026#39;明天\u0026#39;\u0026#34;, \u0026#34;enum\u0026#34;: [\u0026#34;今天\u0026#34;, \u0026#34;明天\u0026#34;, \u0026#34;后天\u0026#34;] } }, \u0026#34;required\u0026#34;: [\u0026#34;city\u0026#34;] # 必填参数 } } 编写优质的工具描述: 1.描述要具体,例如:\n不好的描述：查询天气 好的描述：获取指定城市在特定日期的天气信息，包括温度、湿度、风速和天气状况（晴、雨、多云等） 2.参数描述要具体:不好的参数描述：\n1 \u0026#34;city\u0026#34;: {\u0026#34;type\u0026#34;: \u0026#34;string\u0026#34;} 好的参数描述：\n1 2 3 4 \u0026#34;city\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;string\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;完整的城市名称，如\u0026#39;北京市\u0026#39;、\u0026#39;上海市\u0026#39;。不要使用简称或拼音。\u0026#34; } 3.使用枚举项限制选项 对于有限的选择,是一种很好的约束手段:\n1 2 3 4 5 6 \u0026#34;unit\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;string\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;温度单位\u0026#34;, \u0026#34;enum\u0026#34;: [\u0026#34;celsius\u0026#34;, \u0026#34;fahrenheit\u0026#34;], \u0026#34;default\u0026#34;: \u0026#34;celsius\u0026#34; } 4.提供示例值 在描述中提供示例,帮助LLM理解:\n1 2 3 4 \u0026#34;date\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;string\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;日期，格式应为\u0026#39;YYYY-MM-DD\u0026#39;，例如\u0026#39;2024-06-15\u0026#39;\u0026#34; } 示例: 文件读取工具的定义\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 file_tool = { \u0026#34;name\u0026#34;: \u0026#34;read_file\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;读取指定文件的内容\u0026#34;, \u0026#34;parameters\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;object\u0026#34;, \u0026#34;properties\u0026#34;: { \u0026#34;file_path\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;string\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;文件的完整路径，如\u0026#39;/home/user/document.txt\u0026#39;\u0026#34; }, \u0026#34;encoding\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;string\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;文件编码，默认为\u0026#39;utf-8\u0026#39;\u0026#34;, \u0026#34;default\u0026#34;: \u0026#34;utf-8\u0026#34; } }, \u0026#34;required\u0026#34;: [\u0026#34;file_path\u0026#34;] } } 工具定义的验证 定义工具之后,应该进行验证测试:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 def validate_tool_definition(tool_def): \u0026#34;\u0026#34;\u0026#34;验证工具定义是否完整\u0026#34;\u0026#34;\u0026#34; required_fields = [\u0026#34;name\u0026#34;, \u0026#34;description\u0026#34;, \u0026#34;parameters\u0026#34;] for field in required_fields: if field not in tool_def: return False, f\u0026#34;缺少必要字段: {field}\u0026#34; # 检查参数结构 if \u0026#34;properties\u0026#34; not in tool_def[\u0026#34;parameters\u0026#34;]: return False, \u0026#34;参数定义缺少properties字段\u0026#34; return True, \u0026#34;工具定义完整\u0026#34; # 测试验证 is_valid, message = validate_tool_definition(weather_tool) print(f\u0026#34;验证结果: {is_valid}, 消息: {message}\u0026#34;) 参数提取的验证 LLM从问题中提取出参数之后,会对这些参数进行验证和处理,保证工具能正确执行\n参数提取过程: 分析输出并提取需要的参数:\n参数提取的挑战: 信息缺失:没有提供所有的必须信息 格式不符:提供的格式与定义不符 歧义解析:同一信息可能由歧义 上下文依赖:参数需要结合历史对话理解\n参数验证方法: 1.类型验证:验证参数类型符合要求\ndef validate_parameters(params, tool_def): \u0026quot;\u0026quot;\u0026quot;验证参数类型\u0026quot;\u0026quot;\u0026quot; errors = [] for param_name, param_def in tool_def[\u0026quot;parameters\u0026quot;]\t[\u0026quot;properties\u0026quot;].items(): if param_name in params: param_value = params[param_name] expected_type = param_def.get(\u0026quot;type\u0026quot;) # 类型检查 if expected_type == \u0026quot;string\u0026quot; and not isinstance(param_value, str): errors.append(f\u0026quot;参数'{param_name}'应为字符串类型\u0026quot;) elif expected_type == \u0026quot;integer\u0026quot; and not isinstance(param_value, int): errors.append(f\u0026quot;参数'{param_name}'应为整数类型\u0026quot;) elif expected_type == \u0026quot;number\u0026quot; and not isinstance(param_value, (int, float)): errors.append(f\u0026quot;参数'{param_name}'应为数字类型\u0026quot;) elif expected_type == \u0026quot;boolean\u0026quot; and not isinstance(param_value, bool): errors.append(f\u0026quot;参数'{param_name}'应为布尔类型\u0026quot;) return errors 2.范围验证:检查参数值是否在允许范围内:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 def validate_range(params, tool_def): \u0026#34;\u0026#34;\u0026#34;验证参数范围\u0026#34;\u0026#34;\u0026#34; errors = [] for param_name, param_def in tool_def[\u0026#34;parameters\u0026#34;][\u0026#34;properties\u0026#34;].items(): if param_name in params: param_value = params[param_name] # 检查最小值 if \u0026#34;minimum\u0026#34; in param_def and param_value \u0026lt; param_def[\u0026#34;minimum\u0026#34;]: errors.append(f\u0026#34;参数\u0026#39;{param_name}\u0026#39;不能小于{param_def[\u0026#39;minimum\u0026#39;]}\u0026#34;) # 检查最大值 if \u0026#34;maximum\u0026#34; in param_def and param_value \u0026gt; param_def[\u0026#34;maximum\u0026#34;]: errors.append(f\u0026#34;参数\u0026#39;{param_name}\u0026#39;不能大于{param_def[\u0026#39;maximum\u0026#39;]}\u0026#34;) # 检查枚举值 if \u0026#34;enum\u0026#34; in param_def and param_value not in param_def[\u0026#34;enum\u0026#34;]: errors.append(f\u0026#34;参数\u0026#39;{param_name}\u0026#39;必须是{param_def[\u0026#39;enum\u0026#39;]}中的一个\u0026#34;) return errors 3.必填参数检验: 所有必填参数都已提供\n完整的参数检验系统:类型,范围,必需值\n1 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 class ParameterValidator: \u0026#34;\u0026#34;\u0026#34;参数验证器\u0026#34;\u0026#34;\u0026#34; def __init__(self, tool_def): self.tool_def = tool_def def validate(self, params): \u0026#34;\u0026#34;\u0026#34;执行完整的参数验证\u0026#34;\u0026#34;\u0026#34; all_errors = [] # 检查必填参数 required_errors = self.validate_required(params) all_errors.extend(required_errors) # 检查参数类型 type_errors = self.validate_type(params) all_errors.extend(type_errors) # 检查参数范围 range_errors = self.validate_range(params) all_errors.extend(range_errors) # 检查额外参数（未定义的参数） extra_errors = self.validate_extra(params) all_errors.extend(extra_errors) return len(all_errors) == 0, all_errors def validate_required(self, params): \u0026#34;\u0026#34;\u0026#34;验证必填参数\u0026#34;\u0026#34;\u0026#34; errors = [] required_params = self.tool_def[\u0026#34;parameters\u0026#34;].get(\u0026#34;required\u0026#34;, []) for param_name in required_params: if param_name not in params or params[param_name] is None: errors.append(f\u0026#34;缺少必填参数: {param_name}\u0026#34;) return errors def validate_type(self, params): \u0026#34;\u0026#34;\u0026#34;验证参数类型\u0026#34;\u0026#34;\u0026#34; errors = [] for param_name, param_value in params.items(): if param_name in self.tool_def[\u0026#34;parameters\u0026#34;][\u0026#34;properties\u0026#34;]: param_def = self.tool_def[\u0026#34;parameters\u0026#34;][\u0026#34;properties\u0026#34;][param_name] expected_type = param_def.get(\u0026#34;type\u0026#34;) if expected_type == \u0026#34;string\u0026#34; and not isinstance(param_value, str): errors.append(f\u0026#34;参数\u0026#39;{param_name}\u0026#39;应为字符串类型，实际为{type(param_value).__name__}\u0026#34;) elif expected_type == \u0026#34;integer\u0026#34; and not isinstance(param_value, int): errors.append(f\u0026#34;参数\u0026#39;{param_name}\u0026#39;应为整数类型，实际为{type(param_value).__name__}\u0026#34;) elif expected_type == \u0026#34;number\u0026#34; and not isinstance(param_value, (int, float)): errors.append(f\u0026#34;参数\u0026#39;{param_name}\u0026#39;应为数字类型，实际为{type(param_value).__name__}\u0026#34;) elif expected_type == \u0026#34;boolean\u0026#34; and not isinstance(param_value, bool): errors.append(f\u0026#34;参数\u0026#39;{param_name}\u0026#39;应为布尔类型，实际为{type(param_value).__name__}\u0026#34;) return errors def validate_range(self, params): \u0026#34;\u0026#34;\u0026#34;验证参数范围\u0026#34;\u0026#34;\u0026#34; errors = [] for param_name, param_value in params.items(): if param_name in self.tool_def[\u0026#34;parameters\u0026#34;][\u0026#34;properties\u0026#34;]: param_def = self.tool_def[\u0026#34;parameters\u0026#34;][\u0026#34;properties\u0026#34;][param_name] # 检查最小值 if \u0026#34;minimum\u0026#34; in param_def and param_value \u0026lt; param_def[\u0026#34;minimum\u0026#34;]: errors.append(f\u0026#34;参数\u0026#39;{param_name}\u0026#39;不能小于{param_def[\u0026#39;minimum\u0026#39;]}\u0026#34;) # 检查最大值 if \u0026#34;maximum\u0026#34; in param_def and param_value \u0026gt; param_def[\u0026#34;maximum\u0026#34;]: errors.append(f\u0026#34;参数\u0026#39;{param_name}\u0026#39;不能大于{param_def[\u0026#39;maximum\u0026#39;]}\u0026#34;) # 检查枚举值 if \u0026#34;enum\u0026#34; in param_def and param_value not in param_def[\u0026#34;enum\u0026#34;]: errors.append(f\u0026#34;参数\u0026#39;{param_name}\u0026#39;必须是{param_def[\u0026#39;enum\u0026#39;]}中的一个\u0026#34;) return errors def validate_extra(self, params): \u0026#34;\u0026#34;\u0026#34;检查未定义的额外参数\u0026#34;\u0026#34;\u0026#34; errors = [] defined_params = set(self.tool_def[\u0026#34;parameters\u0026#34;][\u0026#34;properties\u0026#34;].keys()) provided_params = set(params.keys()) extra_params = provided_params - defined_params if extra_params: errors.append(f\u0026#34;提供了未定义的参数: {\u0026#39;, \u0026#39;.join(extra_params)}\u0026#34;) return errors # 使用示例 validator = ParameterValidator(weather_tool) params = {\u0026#34;city\u0026#34;: \u0026#34;北京\u0026#34;, \u0026#34;date\u0026#34;: \u0026#34;明天\u0026#34;, \u0026#34;extra\u0026#34;: \u0026#34;不应该有的参数\u0026#34;} is_valid, errors = validator.validate(params) if is_valid: print(\u0026#34;参数验证通过\u0026#34;) else: print(\u0026#34;参数验证失败:\u0026#34;) for error in errors: print(f\u0026#34; - {error}\u0026#34;) 参数提取和验证的完整流程\n参数修正的策略 询问客户:直接向客户询问缺失和错误的参数 使用默认值:可以预先设定默认值 智能推断:根据上下文推断合理的参数值 格式转换:将用户提供的格式转化成工具要求的格式\n错误处理和重试机制 工具调用可能会失败,健全的错误处理和重试机制是构建可靠的AI Agent的挂件\n常见的工具调用错误: 网络错误:API调用超时,连接错误 参数错误:参数验证失败,格式不正确 权限错误:API密钥无效,权限不足 资源错误:服务不可达,达到调用限制 逻辑错误:工具内部逻辑错误\n错误处理策略 1.分级错误处理: 根据错误类型采取不同的处理策略\nclass ErrorHandler: \u0026quot;\u0026quot;\u0026quot;错误处理器\u0026quot;\u0026quot;\u0026quot; def handle(self, error, context): \u0026quot;\u0026quot;\u0026quot;处理错误\u0026quot;\u0026quot;\u0026quot; error_type = self.classify_error(error) if error_type == \u0026quot;network\u0026quot;: return self.handle_network_error(error, context) elif error_type == \u0026quot;parameter\u0026quot;: return self.handle_parameter_error(error, context) elif error_type == \u0026quot;authentication\u0026quot;: return self.handle_auth_error(error, context) elif error_type == \u0026quot;resource\u0026quot;: return self.handle_resource_error(error, context) else: return self.handle_unknown_error(error, context) def classify_error(self, error): \u0026quot;\u0026quot;\u0026quot;分类错误类型\u0026quot;\u0026quot;\u0026quot; error_str = str(error).lower() if any(word in error_str for word in [\u0026quot;timeout\u0026quot;, \u0026quot;connection\u0026quot;, \u0026quot;network\u0026quot;]): return \u0026quot;network\u0026quot; elif any(word in error_str for word in [\u0026quot;parameter\u0026quot;, \u0026quot;invalid\u0026quot;, \u0026quot;missing\u0026quot;]): return \u0026quot;parameter\u0026quot; elif any(word in error_str for word in [\u0026quot;auth\u0026quot;, \u0026quot;key\u0026quot;, \u0026quot;permission\u0026quot;]): return \u0026quot;authentication\u0026quot; elif any(word in error_str for word in [\u0026quot;limit\u0026quot;, \u0026quot;quota\u0026quot;, \u0026quot;unavailable\u0026quot;]): return \u0026quot;resource\u0026quot; else: return \u0026quot;unknown\u0026quot; def handle_network_error(self, error, context): \u0026quot;\u0026quot;\u0026quot;处理网络错误\u0026quot;\u0026quot;\u0026quot; return { \u0026quot;success\u0026quot;: False, \u0026quot;error\u0026quot;: \u0026quot;网络连接失败，请检查网络后重试\u0026quot;, \u0026quot;retryable\u0026quot;: True, \u0026quot;retry_after\u0026quot;: 5 # 5秒后重试 } def handle_parameter_error(self, error, context): \u0026quot;\u0026quot;\u0026quot;处理参数错误\u0026quot;\u0026quot;\u0026quot; return { \u0026quot;success\u0026quot;: False, \u0026quot;error\u0026quot;: f\u0026quot;参数错误: {str(error)}\u0026quot;, \u0026quot;retryable\u0026quot;: False, \u0026quot;suggestion\u0026quot;: \u0026quot;请检查输入参数是否正确\u0026quot; } # 其他错误处理方法类似... 2.用户友好的错误信息 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 def user_friendly_error(error): \u0026#34;\u0026#34;\u0026#34;生成用户友好的错误消息\u0026#34;\u0026#34;\u0026#34; error_map = { \u0026#34;timeout\u0026#34;: \u0026#34;请求超时，请稍后重试\u0026#34;, \u0026#34;connection_error\u0026#34;: \u0026#34;网络连接失败，请检查网络\u0026#34;, \u0026#34;invalid_api_key\u0026#34;: \u0026#34;API 密钥无效，请检查配置\u0026#34;, \u0026#34;rate_limit_exceeded\u0026#34;: \u0026#34;调用频率过高，请稍后再试\u0026#34;, \u0026#34;invalid_parameters\u0026#34;: \u0026#34;输入参数不正确，请检查后重试\u0026#34; } error_str = str(error).lower() for key, message in error_map.items(): if key in error_str: return message return \u0026#34;系统繁忙，请稍后重试\u0026#34; 重试机制 1.指数退避重试: 指的是等待时间随着重试次数呈指数级增长 等待时间:指数退避+随机抖动\n1 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 import time import random class ExponentialBackoffRetry: \u0026#34;\u0026#34;\u0026#34;指数退避重试\u0026#34;\u0026#34;\u0026#34; def __init__(self, max_retries=3, base_delay=1, max_delay=30): self.max_retries = max_retries self.base_delay = base_delay self.max_delay = max_delay def retry(self, func, *args, **kwargs): \u0026#34;\u0026#34;\u0026#34;执行带重试的函数调用\u0026#34;\u0026#34;\u0026#34; last_error = None for attempt in range(self.max_retries): try: return func(*args, **kwargs) except Exception as e: last_error = e # 检查是否可重试 if not self.is_retryable(e): raise # 最后一次尝试，不再重试 if attempt == self.max_retries - 1: break # 计算等待时间（指数退避 + 随机抖动） delay = min( self.base_delay * (2 ** attempt) + random.uniform(0, 1), self.max_delay ) print(f\u0026#34;第{attempt + 1}次尝试失败，{delay:.1f}秒后重试...\u0026#34;) time.sleep(delay) # 所有重试都失败 raise last_error def is_retryable(self, error): \u0026#34;\u0026#34;\u0026#34;判断错误是否可重试\u0026#34;\u0026#34;\u0026#34; error_str = str(error).lower() retryable_errors = [\u0026#34;timeout\u0026#34;, \u0026#34;connection\u0026#34;, \u0026#34;busy\u0026#34;, \u0026#34;temporarily\u0026#34;] for retryable_error in retryable_errors: if retryable_error in error_str: return True return False # 使用示例 retry = ExponentialBackoffRetry(max_retries=3) def call_weather_api(city, date): \u0026#34;\u0026#34;\u0026#34;调用天气API（模拟可能失败）\u0026#34;\u0026#34;\u0026#34; # 模拟API调用 if random.random() \u0026lt; 0.3: # 30%概率失败 raise ConnectionError(\u0026#34;API连接超时\u0026#34;) return {\u0026#34;temperature\u0026#34;: 25, \u0026#34;condition\u0026#34;: \u0026#34;晴\u0026#34;} try: result = retry.retry(call_weather_api, \u0026#34;北京\u0026#34;, \u0026#34;今天\u0026#34;) print(f\u0026#34;成功获取天气: {result}\u0026#34;) except Exception as e: print(f\u0026#34;获取天气失败: {e}\u0026#34;) 2.熔断器模式 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 class CircuitBreaker: \u0026#34;\u0026#34;\u0026#34;熔断器\u0026#34;\u0026#34;\u0026#34; def __init__(self, failure_threshold=5, reset_timeout=60): self.failure_threshold = failure_threshold self.reset_timeout = reset_timeout self.failure_count = 0 self.last_failure_time = None self.state = \u0026#34;closed\u0026#34; # closed, open, half-open def execute(self, func, *args, **kwargs): \u0026#34;\u0026#34;\u0026#34;通过熔断器执行函数\u0026#34;\u0026#34;\u0026#34; if self.state == \u0026#34;open\u0026#34;: # 检查是否应该尝试恢复 if self.should_try_reset(): self.state = \u0026#34;half-open\u0026#34; else: raise Exception(\u0026#34;熔断器开启，服务暂时不可用\u0026#34;) try: result = func(*args, **kwargs) # 成功调用，重置状态 if self.state == \u0026#34;half-open\u0026#34;: self.state = \u0026#34;closed\u0026#34; self.failure_count = 0 return result except Exception as e: self.record_failure() raise e def record_failure(self): \u0026#34;\u0026#34;\u0026#34;记录失败\u0026#34;\u0026#34;\u0026#34; self.failure_count += 1 self.last_failure_time = time.time() if self.failure_count \u0026gt;= self.failure_threshold: self.state = \u0026#34;open\u0026#34; def should_try_reset(self): \u0026#34;\u0026#34;\u0026#34;检查是否应该尝试重置\u0026#34;\u0026#34;\u0026#34; if self.last_failure_time is None: return True elapsed = time.time() - self.last_failure_time return elapsed \u0026gt; self.reset_timeout # 使用示例 breaker = CircuitBreaker(failure_threshold=3, reset_timeout=30) def unreliable_service(): \u0026#34;\u0026#34;\u0026#34;模拟不可靠的服务\u0026#34;\u0026#34;\u0026#34; if random.random() \u0026lt; 0.5: raise Exception(\u0026#34;服务异常\u0026#34;) return \u0026#34;服务正常\u0026#34; for i in range(10): try: result = breaker.execute(unreliable_service) print(f\u0026#34;调用{i+1}: {result}\u0026#34;) except Exception as e: print(f\u0026#34;调用{i+1}: {e}\u0026#34;) time.sleep(1) 完整的工具框架 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 class ToolExecutor: \u0026#34;\u0026#34;\u0026#34;工具执行器\u0026#34;\u0026#34;\u0026#34; def __init__(self): self.tools = {} # 注册的工具 self.validator = ParameterValidator self.error_handler = ErrorHandler() self.retry = ExponentialBackoffRetry() self.breaker = CircuitBreaker() def register_tool(self, name, tool_def, func): \u0026#34;\u0026#34;\u0026#34;注册工具\u0026#34;\u0026#34;\u0026#34; self.tools[name] = { \u0026#34;definition\u0026#34;: tool_def, \u0026#34;function\u0026#34;: func } def execute_tool(self, tool_name, params): \u0026#34;\u0026#34;\u0026#34;执行工具\u0026#34;\u0026#34;\u0026#34; if tool_name not in self.tools: return { \u0026#34;success\u0026#34;: False, \u0026#34;error\u0026#34;: f\u0026#34;工具\u0026#39;{tool_name}\u0026#39;未注册\u0026#34; } tool_info = self.tools[tool_name] tool_def = tool_info[\u0026#34;definition\u0026#34;] tool_func = tool_info[\u0026#34;function\u0026#34;] # 验证参数 validator = self.validator(tool_def) is_valid, errors = validator.validate(params) if not is_valid: return { \u0026#34;success\u0026#34;: False, \u0026#34;error\u0026#34;: \u0026#34;参数验证失败\u0026#34;, \u0026#34;details\u0026#34;: errors } # 通过熔断器执行（带重试） try: def execute_with_retry(): return self.breaker.execute( lambda: self.retry.retry(tool_func, **params) ) result = execute_with_retry() return { \u0026#34;success\u0026#34;: True, \u0026#34;result\u0026#34;: result, \u0026#34;tool\u0026#34;: tool_name } except Exception as e: # 错误处理 error_response = self.error_handler.handle(e, { \u0026#34;tool\u0026#34;: tool_name, \u0026#34;params\u0026#34;: params }) return { \u0026#34;success\u0026#34;: False, \u0026#34;error\u0026#34;: error_response[\u0026#34;error\u0026#34;], \u0026#34;retryable\u0026#34;: error_response.get(\u0026#34;retryable\u0026#34;, False), \u0026#34;suggestion\u0026#34;: error_response.get(\u0026#34;suggestion\u0026#34;, \u0026#34;\u0026#34;) } def handle_user_request(self, user_input): \u0026#34;\u0026#34;\u0026#34;处理用户请求（简化版）\u0026#34;\u0026#34;\u0026#34; # 1. 让LLM选择工具和提取参数（这里简化处理） # 实际应用中，这里会调用LLM进行工具选择和参数提取 # 模拟LLM的输出 if \u0026#34;天气\u0026#34; in user_input: tool_name = \u0026#34;get_weather\u0026#34; # 简单提取城市（实际应用中LLM会做得更好） if \u0026#34;北京\u0026#34; in user_input: params = {\u0026#34;city\u0026#34;: \u0026#34;北京\u0026#34;, \u0026#34;date\u0026#34;: \u0026#34;今天\u0026#34;} elif \u0026#34;上海\u0026#34; in user_input: params = {\u0026#34;city\u0026#34;: \u0026#34;上海\u0026#34;, \u0026#34;date\u0026#34;: \u0026#34;今天\u0026#34;} else: params = {\u0026#34;city\u0026#34;: \u0026#34;北京\u0026#34;, \u0026#34;date\u0026#34;: \u0026#34;今天\u0026#34;} # 默认值 else: return \u0026#34;抱歉，我无法处理这个请求\u0026#34; # 2. 执行工具 result = self.execute_tool(tool_name, params) # 3. 生成最终回答 if result[\u0026#34;success\u0026#34;]: weather = result[\u0026#34;result\u0026#34;] return f\u0026#34;{params[\u0026#39;city\u0026#39;]}今天天气：{weather[\u0026#39;condition\u0026#39;]}，温度{weather[\u0026#39;temperature\u0026#39;]}°C\u0026#34; else: return f\u0026#34;获取天气信息失败：{result[\u0026#39;error\u0026#39;]}\u0026#34; # 使用示例 executor = ToolExecutor() # 注册天气工具 def mock_weather_api(city, date): \u0026#34;\u0026#34;\u0026#34;模拟天气API\u0026#34;\u0026#34;\u0026#34; # 模拟API调用延迟 time.sleep(0.1) return {\u0026#34;temperature\u0026#34;: 22, \u0026#34;condition\u0026#34;: \u0026#34;多云\u0026#34;} executor.register_tool(\u0026#34;get_weather\u0026#34;, weather_tool, mock_weather_api) # 处理用户请求 response = executor.handle_user_request(\u0026#34;北京今天天气怎么样？\u0026#34;) print(response) ","permalink":"https://tuanzi-wow.cn/agent/function-calling/","summary":"\u003cp\u003e//我说句实在话,这个菜鸟教程写的好简单也挺烂的,不过很容易看懂吧,还真是菜鸟教程,而且代码又臭又长\u003c/p\u003e\n\u003ch1 id=\"function-calling工具调用\"\u003eFunction Calling(工具调用)\u003c/h1\u003e\n\u003cp\u003e是LLM能够调用外部工具的核心机制,允许模型决定什么时候调用工具,调用什么工具,以及传递什么参数\u003c/p\u003e","title":"Function Calling（工具调用）"},{"content":"1.合并区间 题目描述： 以数组 intervals 表示若干个区间的集合，其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间，并返回 一个不重叠的区间数组，该数组需恰好覆盖输入中的所有区间 。\n示例 1：\n1 2 3 输入：intervals = [[1,3],[2,6],[8,10],[15,18]] 输出：[[1,6],[8,10],[15,18]] 解释：区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6]. 示例 2：\n1 2 3 输入：intervals = [[1,4],[4,5]] 输出：[[1,5]] 解释：区间 [1,4] 和 [4,5] 可被视为重叠区间。 示例 3：\n1 2 3 输入：intervals = [[4,7],[1,4]] 输出：[[1,7]] 解释：区间 [1,4] 和 [4,7] 可被视为重叠区间。 提示：\n1 \u0026lt;= intervals.length \u0026lt;= 104 intervals[i].length == 2 0 \u0026lt;= starti \u0026lt;= endi \u0026lt;= 104 解1.排序 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution: def merge(self, intervals: List[List[int]]) -\u0026gt; List[List[int]]: intervals.sort(key=lambda x:x[0]) if len(intervals)==1: return intervals ans=[] ans.append(intervals[0]) cur=0 for i in range(1,len(intervals)): if intervals[i][0]\u0026lt;=ans[-1][1]: ans[-1][1] = max(ans[-1][1], intervals[i][1]) else: ans.append(intervals[i]) return ans 解2. 通过一个cnt数组记录落位，cnt[i]\u0026gt;1的连续区间即为最终的区间 方法：对于一个区间[start,end]，从cnt[start],cnt[start+1],\u0026hellip;,cnt[end]全部加1 缺陷：[1,2],[3,4]本无法合并，按上述方法却可以合并，解决：*2\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Solution: def merge(self, intervals: List[List[int]]) -\u0026gt; List[List[int]]: mx = max(p[1] for p in intervals) diff = [0] * (mx * 2 + 2) for start, end in intervals: # 把区间 [start*2, end*2] 增加 1 diff[start * 2] += 1 diff[end * 2 + 1] -= 1 ans = [] sum_d = 0 start = -1 # -1 表示尚未遇到合并后的区间左端点 for i, d in enumerate(diff): sum_d += d # 计算 diff 的前缀和 if sum_d \u0026gt; 0: if start \u0026lt; 0: start = i # 合并后的区间左端点 elif start \u0026gt;= 0: # i-1 是合并后的区间右端点 # 由于乘 2 操作，区间左右端点都是偶数，所以 i-1 是偶数，i 是奇数，(i-1)/2 == floor(i/2) ans.append([start // 2, i // 2]) start = -1 # 注：最后一轮循环 sum_d == 0，我们不会漏掉最后一个区间 return ans 轮转数组 题目描述: 给定一个整数数组 nums，将数组中的元素向右轮转 k 个位置，其中 k 是非负数。\n示例 1:\n1 2 3 4 5 6 输入: nums = [1,2,3,4,5,6,7], k = 3 输出: [5,6,7,1,2,3,4] 解释: 向右轮转 1 步: [7,1,2,3,4,5,6] 向右轮转 2 步: [6,7,1,2,3,4,5] 向右轮转 3 步: [5,6,7,1,2,3,4] 示例 2:\n1 2 3 4 5 输入：nums = [-1,-100,3,99], k = 2 输出：[3,99,-1,-100] 解释: 向右轮转 1 步: [99,-1,-100,3] 向右轮转 2 步: [3,99,-1,-100] 提示：\n1 \u0026lt;= nums.length \u0026lt;= 105 -231 \u0026lt;= nums[i] \u0026lt;= 231 - 1 0 \u0026lt;= k \u0026lt;= 105 题解：自己看，很简单 1 2 3 4 5 6 7 8 9 class Solution: def rotate(self, nums: List[int], k: int) -\u0026gt; None: \u0026#34;\u0026#34;\u0026#34; Do not return anything, modify nums in-place instead. \u0026#34;\u0026#34;\u0026#34; k=k%len(nums) p=nums[-k:] del nums[-k:] nums[0:0]=p 3.除了自身以数组的乘积 题目描述 给你一个整数数组 nums，返回 数组 answer ，其中 answer[i] 等于 nums 中除了 nums[i] 之外其余各元素的乘积 。\n题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。\n请 **不要使用除法，**且在 O(n) 时间复杂度内完成此题。\n示例 1:\n1 2 输入: nums = [1,2,3,4] 输出: [24,12,8,6] 示例 2:\n1 2 输入: nums = [-1,1,0,-3,3] 输出: [0,0,9,0,0] 题解： 计算前乘积和后乘积，两者相乘即可\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Solution: def productExceptSelf(self, nums: List[int]) -\u0026gt; List[int]: m = len(nums) suf = [1]*m for i in range(m-2, -1, -1): suf[i] = suf[i+1]*nums[i+1] per = 1 for i, num in enumerate(nums): suf[i] *= per per *= num return suf #time O(m) #space O(1) 4.Hard 缺失的第一个正数 题目描述： 给你一个未排序的整数数组 nums ，请你找出其中没有出现的最小的正整数。\n请你实现时间复杂度为 O(n) 并且只使用常数级别额外空间的解决方案。\n示例 1：\n1 2 3 输入：nums = [1,2,0] 输出：3 解释：范围 [1,2] 中的数字都在数组中。 示例 2：\n1 2 3 输入：nums = [3,4,-1,1] 输出：2 解释：1 在数组中，但 2 没有。 示例 3：\n1 2 3 输入：nums = [7,8,9,11,12] 输出：1 解释：最小的正数 1 没有出现。 题解 意识到ans \u0026lt;= len(nums)，然后依次判断i是否in nums即可\n1 2 3 4 5 6 class Solution: def firstMissingPositive(self, nums: List[int]) -\u0026gt; int: a=set(nums) for i in range(1,len(nums)+2): if i not in a: return i 还有一个题解我看着很有思路，虽然执行时间更长，但是包含了本地标记的思想，通过hashsize进行本地标记，挺有意思的，我把代码贴上去\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution: def firstMissingPositive(self, nums: List[int]) -\u0026gt; int: n = len(nums) hash_size = n + 1 # 可能的答案为1, 2, ..., n + 1 # 处理掉取值范围外的数 for i in range(n): if nums[i] \u0026lt;= 0 or nums[i] \u0026gt;= hash_size: nums[i] = 0 for i in range(n): if nums[i] % hash_size != 0: pos = (nums[i] % hash_size) - 1 # 先取余再加，防止数字过大 nums[pos] = (nums[pos] % hash_size) + hash_size for i in range(n): if nums[i] \u0026lt; hash_size: return i + 1 return hash_size ","permalink":"https://tuanzi-wow.cn/leetcode%E5%88%B7%E9%A2%98%E8%AE%B0%E5%BD%95/hot100/hot100-array/","summary":"\u003ch3 id=\"1合并区间\"\u003e1.合并区间\u003c/h3\u003e\n\u003ch4 id=\"题目描述\"\u003e题目描述：\u003c/h4\u003e\n\u003cp\u003e以数组 \u003ccode\u003eintervals\u003c/code\u003e 表示若干个区间的集合，其中单个区间为 \u003ccode\u003eintervals[i] = [starti, endi]\u003c/code\u003e 。请你合并所有重叠的区间，并返回 \u003cem\u003e一个不重叠的区间数组，该数组需恰好覆盖输入中的所有区间\u003c/em\u003e 。\u003c/p\u003e","title":"HOT100 · 普通数组"},{"content":"第一章：NLP基础概念 NPL: Natural Language Processing(自然语言处理) 使计算机能够理解和处理人类语言,实现人机交流 从早期的基于规则的方法,到后来的统计学习方法,再到当前深度学习技术的广泛应用 文本表示作为NPL的核心技术之一,对于NPL系统的性能提升具有决定性的作用\n1.1 什么是NLP 让计算机理解,解释和生成人类语言的技术.通过计算机程序来模拟热泪对于语言的认知和使用过程,使一门跨多种学科的技术\nNLP技术使得计算机能够执行各种复杂的语言处理任务,这些任务不仅要求计算机能够识别和处理语言的表层结构,更重要的是理解语言背后的深层含义,包括语义、语境、情感和文化等方面的复杂因素.\n进入深度学习阶段,NLP获得了显著的进步目的,但是NLP仍然面临着大量的挑战没比如:如处理歧义性、理解抽象概念、处理隐喻和讽刺等\n1.2 NLP发展历程 规则基础方法-\u0026gt;符号注意和统计方法-\u0026gt;机器学习和深度学习\n早期探索 图灵提出: 他说，如果一台机器可以通过使用打字机成为对话的一部分，并且能够完全模仿人类，没有明显的差异，那么机器可以被认为是能够思考的。\n这是判断机器是否能够展现出与人类不可区分的智能行为的测试 同时,诺姆·乔姆斯基提出了生成语法理论 这一时期的机器翻译系统非常简单，主要依赖字典查找和基本的词序规则来进行翻译\n符号主义与统计探索 主要分为:符号主义和统计方法两大阵营 符号主义关注形式语言和生成语法 统计方法关注统计与概率方法 1980年代,统计模型开始取代复杂的\u0026quot;手写\u0026quot;规则\n机器学习和深度学习 深度学习模型如循环神经网络（Recurrent Neural Network，RNN）、长短时记忆网络（Long Short-Term Memory，LSTM）和注意力机制等技术被广泛应用于 NLP 任务中 Word2Vec模型的提出开创了词向量表示的新时代 2018年.BERT模型引领了预训练语言模型的新浪潮 近年来,基于Transformer\n1.3 NLP任务 涵盖了从文本的基本处理到复杂的语义理解和生成的各个方面 核心任务:包括但不限于中文分词、子词切分、词性标注、文本分类、实体识别、关系抽取、文本摘要、机器翻译以及自动问答系统的开发\n1.3.1 中文分词(Chinese Word Segmentation, CWS) 中文词与词之间没有明确的分割 中文分词成为了中午文本处理的首要步骤,目的是将连续的中文文本切分成有意义的词汇序列\n正确的分词结果对于后续的词性标注、实体识别、句法分析等任务至关重要。如果分词不准确，将直接影响到整个文本处理流程的效果。\n1.3.2 字词区分(Subword Segmentation) 子词切分（Subword Segmentation）是 NLP 领域中的一种常见的文本预处理技术，旨在将词汇进一步分解为更小的单位，即子词。\n子词切分特别适用于处理词汇稀疏问题，即当遇到罕见词或未见过的新词时，能够通过已知的子词单位来理解或生成这些词汇。子词切分在处理那些拼写复杂、合成词多的语言（如德语）或者在预训练语言模型（如BERT、GPT系列）中尤为重要,例如:\n1 2 3 4 输入：unhappiness 不使用子词切分：整个单词作为一个单位，输出：“unhappiness” 使用子词切分（假设BPE算法）：单词被分割为：“un”、“happi”、“ness”Copy to clipboardErrorCopied 在这个例子中，通过子词切分，“unhappiness”这个词被分解成了三个部分：前缀“un”表示否定，“happi”是“happy”的词根变体，表示幸福，“ness”是名词后缀，表示状态。即使模型从未见过“unhappiness”这个完整的单词，它也可以通过这些已知的子词来理解其大致意思为“不幸福的状态”。\n子词切分的方法有很多种，常见的有Byte Pair Encoding (BPE)、WordPiece、Unigram、SentencePiece等。 这些方法的基本思想是将单词分解成更小的、频繁出现的片段，这些片段可以是单个字符、字符组合或者词根和词缀。\n1.3.3 词性标注(Part-of-Speech Tagging，POS Tagging) 它的目标是为文本中的每个单词分配一个词性标签，如名词、动词、形容词等\n词性标注通常依赖于机器学习模型，如隐马尔可夫模型（Hidden Markov Model，HMM）、条件随机场（Conditional Random Field，CRF）或者基于深度学习的循环神经网络 RNN 和长短时记忆网络 LSTM 等。这些模型通过学习大量的标注数据来预测新句子中每个单词的词性。\n1.3.4 文本分类 到将给定的文本自动分配到一个或多个预定义的类别中。这项技术广泛应用于各种场景 关键:文本分类的关键在于理解文本的含义和上下文，并基于此将文本映射到特定的类别 文本分类任务的成功关键在于选择合适的特征表示和分类算法，以及拥有高质量的训练数据 随着深度学习技术的发展，使用神经网络进行文本分类已经成为一种趋势，它们能够捕捉到文本数据中的复杂模式和语义信息\n1.3.5 实体识别(Named Entity Recognition, NER) 也称为命名实体类别 旨在自动识别文本中具有特定意义的实体，并将它们分类为预定义的类别\n1.3.6 关系抽取(Relation Extraction) 它的目标是从文本中识别实体之间的语义关系。这些关系可以是因果关系、拥有关系、亲属关系、地理位置关系等\n1.3.7 文本摘要(Text Summarization) 目的是生成一段简洁准确的摘要，来概括原文的主要内容 根据生成方式的不同,可以分为两大类: 抽取式摘要(Extractive Summarization) :从原文中选取关键句子和短语来组成摘要 优点:信息来自原文,准确 缺点:拼接,不够流畅\n生成式摘要(Abstractive Summarization): 选择文本片段,重新组织和改写 更具有挑战性,需要理解文本深层含义并且表达相同的信息\n1.3.8 机器翻译(Machine Translation, MT) 指使用计算机程序将一种自然语言（源语言）自动翻译成另一种自然语言（目标语言）的过程\n机器翻译不仅涉及到词汇的直接转换，更重要的是要准确传达源语言文本的语义、风格和文化背景等\n为了提高机器翻译的质量，研究者不断探索新的方法和技术，如基于神经网络的Seq2Seq模型、Transformer模型等，这些模型能够学习到源语言和目标语言之间的复杂映射关系，从而实现更加准确和流畅的翻译\n1.3.9 自动问答(Automatic Question Answering, QA) 旨在使计算机能够理解自然语言提出的问题，并根据给定的数据源自动提供准确的答案 自动问答系统的构建涉及多个NLP子任务，如信息检索、文本理解、知识表示和推理等\n自动问答可分为三类: 检索式问答(Retrieval-based QA):通过搜索引擎等方式从大量文本中检索答案 知识库问答(Knowledge-based QA):通过结构化的知识库回答 社区问答(Community-based QA):依赖于用户生成的问答数据\n1.4 文本表示的发展历程 文本表示的目的是将人类语言的自然形式转化为计算机可以处理的形式，也就是将文本数据数字化，使计算机能够对文本进行有效的分析和处理。文本表示是 NLP 领域中的一项基础性和必要性工作，它直接影响甚至决定着 NLP 系统的质量和性能。\n在 NLP 中，文本表示涉及到将文本中的语言单位（如字、词、短语、句子等）以及它们之间的关系和结构信息转换为计算机能够理解和操作的形式，例如向量、矩阵或其他数据结构。这样的表示不仅需要保留足够的语义信息，以便于后续的 NLP 任务，如文本分类、情感分析、机器翻译等，还需要考虑计算效率和存储效率。\n文本表示的发展历程经历了多个阶段，从早期的基于规则的方法，到统计学习方法，再到当前的深度学习技术，文本表示技术不断演进，为 NLP 的发展提供了强大的支持。\n1.4.1 词向量 向量空间模型（Vector Space Model, VSM）: 是 NLP 领域中一个基础且强大的文本表示方法，最早由哈佛大学Salton提出。向量空间模型通过将文本（包括单词、句子、段落或整个文档）转换为高维空间中的向量来实现文本的数学化表示。在这个模型中，每个维度代表一个特征项（例如，字、词、词组或短语），而向量中的每个元素值代表该特征项在文本中的权重，这种权重通过特定的计算公式（如词频TF、逆文档频率TF-IDF等）来确定，反映了特征项在文本中的重要程度。\n应用: 包括但不限于文本相似度计算、文本分类、信息检索等自然语言处理任务\n问题: 1.最主要的是数据稀疏性和维数灾难问题:特征项数量庞大导致向量维度极高，同时多数元素值为零 2.此外，由于模型基于特征项之间的独立性假设，忽略了文本中的结构信息，如词序和上下文信息，限制了模型的表现力。 3.特征项的选择和权重计算方法的不足也是向量空间模型需要解决的问题\n词汇表: 词汇表是一个包含所有可能出现的词语的集合。在向量空间模型中，每个词对应词汇表中的一个位置，通过这种方式可以将词语转换为向量表示\n改进: 1.改进特征表示方法:如借助图方式,主题方式等进行关键词提取 2.改进和优化特征项权重的计算方式\n1.4.2 语言模型 N-gram模型:基于统计的语言模型 核心思想:基于马尔可夫假设,即一个词的出现概率仅依赖于它前面的N-1个词 例如: 当N=1时，模型称为unigram，仅考虑单个词的概率；当N=2时，称为bigram，考虑前一个词来估计当前词的概率；当N=3时，称为trigram，考虑前两个词来估计第三个词的概率，以此类推N-gram\nN-gram模型通过条件概率链式规则来估计整个句子的概率 对于给定的一个句子，模型会计算每个N-gram出现的条件概率，并将这些概率相乘以得到整个句子的概率, 例如: 对于句子“The quick brown fox”，作为trigram模型，我们会计算 P(\u0026ldquo;brown\u0026rdquo;∣\u0026ldquo;The\u0026rdquo;,\u0026ldquo;quick\u0026rdquo;)P(\u0026quot;brown\u0026quot;∣\u0026quot;The\u0026quot;,\u0026quot;quick\u0026quot;)、P(\u0026ldquo;fox\u0026rdquo;∣\u0026ldquo;quick\u0026rdquo;,\u0026ldquo;brown\u0026rdquo;)P(\u0026quot;fox\u0026quot;∣\u0026quot;quick\u0026quot;,\u0026quot;brown\u0026quot;)等概率，并将它们相乘\n优点:实现简单,容易理解\n缺点:N过大出现数据稀疏性问题;参数空间过大,相同的\n1.4.3 Word2Vec 通过学习词的上下文关系来生成词的密集向量表示 核心思想:利用词在文本中的上下文信息来捕捉词之间的语义关系,从而使得语义相似或相关的词在向量空间中距离较近 主要有两种架构: Word2Vec模型主要有两种架构：连续词袋模型CBOW(Continuous Bag of Words)是根据目标词上下文中的词对应的词向量, 计算并输出目标词的向量表示；Skip-Gram模型与CBOW模型相反, 是利用目标词的向量表示计算上下文中的词向量. 实践验证CBOW适用于小型数据集, 而Skip-Gram在大型语料中表现更好\n优点:向量密集型,减少计算复杂度和存储需求;捕捉词之间的语义关系,可以更好地泛化没见过地词 缺点:基于局部上下文,无法捕捉到长距离的依赖关系\n1.4.4 ElMo(Embeddings from Language Models) 实现了一词多义,静态词向量到动态词向量的跨越式转变\n分为两个阶段: 第1个阶段是利用语言模型进行预训练; 第2个阶段是在做特定任务时, 从预训练网络中提取对应单词的词向量作为新特征补充到下游任务中\n优点:能够捕捉词汇的多义性和上下文信息,生成的词向量也更加丰富准确 缺点:模型复杂度高,训练时间长,计算资源消耗大等等\n","permalink":"https://tuanzi-wow.cn/llm/llm-nlp-basics/","summary":"\u003ch1 id=\"第一章nlp基础概念\"\u003e第一章：NLP基础概念\u003c/h1\u003e\n\u003cp\u003eNPL: Natural Language Processing(自然语言处理)\n使计算机能够理解和处理人类语言,实现人机交流\n从早期的基于规则的方法,到后来的统计学习方法,再到当前深度学习技术的广泛应用\n文本表示作为NPL的核心技术之一,对于NPL系统的性能提升具有决定性的作用\u003c/p\u003e","title":"NLP 基础概念"},{"content":"注意AI更新迭代很快,这些未必具有实效性,请你自己分辨\n基本概念 给LLM提示: 结果质量与Prompt中的信息数量和完善程度有关\n设计有效的提示词以指导模型执行期望任务的方法被称为提示工程\n提示词格式 QA格式\n1 2 Q: \u0026lt;问题\u0026gt;? A: 零样本提示: 直接提示模型提供一个回答,而没有提供任何你希望它完成的任务的示例或者示范 示例\n1 What is prompt engineering? 少样本提示: 提供少样本示例\n小样本可以赋能上下文学习能力\n","permalink":"https://tuanzi-wow.cn/promptengineering/prompt-basics/","summary":"\u003cp\u003e注意AI更新迭代很快,这些未必具有实效性,请你自己分辨\u003c/p\u003e\n\u003ch1 id=\"基本概念\"\u003e基本概念\u003c/h1\u003e\n\u003ch2 id=\"给llm提示\"\u003e给LLM提示:\u003c/h2\u003e\n\u003cp\u003e结果质量与Prompt中的信息数量和完善程度有关\u003c/p\u003e\n\u003cp\u003e设计有效的提示词以指导模型执行期望任务的方法被称为\u003cstrong\u003e提示工程\u003c/strong\u003e\u003c/p\u003e","title":"Prompt Engineering · 基本概念"},{"content":"这台博客跑在一台最便宜的阿里云 ECS 上：2 vCPU、实际可用内存 1.6 GiB、固定 3 Mbps 带宽，约束是「除服务器本身外新增付费 0 元」。这篇文章复盘整个工程链路的四个关键决策，以及它们各自是怎么被现实打脸又修正的。\n最终形态的实测数据：push 后 2 秒完成构建发布与增量索引，站内搜索 p95 6.6 ms，检索服务常驻内存 122 MiB，Lighthouse 四项 100。这些数字后面是 8 个记录在案的缺陷（D32~D39）和三次架构级改判。\n一、为什么不用 GitHub Pages / Vercel 第一个被否掉的是最省事的方案。理由按权重排：\n境内访问不稳。读者在国内，GitHub Pages 的可达性看运气；这篇文章的读者大概率也用不了 Vercel 的默认域名。 检索与统计只能挂第三方。静态托管意味着搜索要么引第三方服务（境外依赖 + 付费 + 隐私），要么不做。自托管后，搜索是 60 行 SQL 的事。 「自有域名 + 自有数据」。备案域名指向自己的服务器，数据在自己手里——这本身就是练习运维的机会。 二、为什么构建放服务器本地（pull 式），而不是境外 Runner 推过来 直觉方案是 GitHub Actions：push 触发构建，构建完 SSH 推产物到服务器。实测否决：境外 Runner 到境内的 SSH 连接间歇丢包，2 分钟的更新 SLA 根本承诺不了。这是一次典型的「把不稳定面摊到整条链路上」的错误——CI 推送意味着每次发布都要赌一次跨境传输。\n改成 pull 式之后，链路变成：push → （Gitee 中转）→ 服务器每 60 秒轮询发现新提交 → 服务器本地 git pull + hugo 构建 → 原子切版。构建从「不可控的境外网络任务」变成「服务器本地 2 秒的确定性操作」，不稳定的国际链路被压缩到一次 git fetch——甚至后来干脆把拉取源切到 Gitee，连这一次赌运气的 fetch 都消掉了。\n这个演进的触发点值得单独说：GitHub 的 HTTPS 对境内 ECS 会间歇性 SYN 黑洞（connect 挂满 127 秒，恰好是 TCP SYN 重传上限），一度让每分钟一次的轮询变成每分钟一封告警邮件。最后的解法不是继续调告警阈值，而是让服务器和 GitHub 解耦：本地 push 同时推 GitHub 和 Gitee，服务器只跟境内的 Gitee 说话。GitHub 退化成纯展示副本——它挂了只影响简历仓库的新鲜度，不影响任何真实功能。\n三、FTS5 为什么必须 jieba 预分词 SQLite FTS5 的默认分词器 unicode61 按 Unicode 词边界切词——对英文完美，对中文灾难：「标准库概览」整句是一个 token。意味着搜「标准库」永远搜不到「标准库概览」里的内容，除非用户输入整句。\n解法是写入索引前先 jieba 分词，把「标准库 概览」以空格分隔喂给 FTS5。两个实现细节：\n查询侧用 cut_for_search（更细粒度），索引侧用精确模式——查询 token 是索引 token 的超集，配合 OR 兜底，命中率优先。 摘要和锚点不交给 FTS5：FTS5 的 snippet() 只能作用于索引列（已分词的文本），展示出来全是断词空格。所以索引列之外另存一张 sections 表（每节的原始文本 + Goldmark 自动生成的锚点 id），命中后由应用层挑最佳小节、拼 \u0026lt;mark\u0026gt; 高亮和「跳转到该节」的锚点。搜索结果的「命中正确段落并跳转」就是这么来的。 实测：站内搜索 p95 6.6 ms（含 jieba 查询分词），增量索引（一篇新文章）2 秒内可搜。\n四、原子发布与失败保护：8 个真实缺陷喂出来的 发布链路的核心不变量只有一句：先建新版本、验证通过才切软链；任何一步失败，线上继续服务旧内容。deploy.sh 的顺序是固定的：flock → 拉取 → hugo 构建到全新 release 目录 → 产物健全性检查 → ln + mv -Tf 原子切软链 → nginx reload → 增量索引 → 清理旧版。\n这套设计的价值不是纸面文章——上线头一天它就被真实触发了三次：\n缺陷 触发 失败保护的表现 D39 测量脚本用 PowerShell 拼探针页 front matter，日期值被解析器拆到下一行，TOML 空值 hugo 构建失败 → 未切版，线上无恙；告警邮件即时送达 D38 备份凭据文件粘贴断行，快照根本没推上远端 巡检新增「备份新鲜度」维度，5 分钟内报「备份失败」 D32 nginx 用 reload 后旧 worker 还持旧配置，自检撞上 404 改为「配置有变化才 restart」，顺带幂等 除此之外还有五个：告警无去抖变成风暴（D35）、去抖的「成功即清零」在边缘抖动下失效（D36）、安装脚本对拉起的 oneshot 只展示不查终态（D34）、logrotate 判据把 grep -q 的方向用反（D33）、探测 URL 漏了 Hugo 的分区前缀导致三轮「探的都是 404」（D41）。\n这些缺陷的共同模式很有意思：没有一个是「代码写错逻辑」，全是「判据与被测对象错位」——探的 URL 不是真实 URL、查的返回码不代表真实状态、比的字符串被编码破坏、认定成功时其实只完成了一半。对应的防回归也被提炼成几条硬规则写进了缺陷档的总纲：判据来自产物扫描不来自意图；装载类判据查终态不查动作返回码；计数断言必须能让脚本停下；去抖必须覆盖恢复侧；生成结构化文本后必须回读关键字段。\n五、为什么不用 Docker / WordPress / 第三方评论 Docker：1.6 GiB 上容器常驻开销大于收益，systemd 单元 + MemoryMax 是更便宜的资源边界。 WordPress/Halo：内容进数据库，「Git 是唯一内容源」就死了——push 更新、回滚、异地备份全部退化。 第三方统计/评论：境外脚本拖慢页面还要隐私政策；navigator.sendBeacon + 一张 SQLite 表，456 KB。 收个尾 这套系统的完整决策链、平台约束（备案拦截、镜像黑洞、内存预算）和运维手册都在仓库里。如果说有什么元教训：约束不是敌人。1.6 GiB 内存逼出了标准库 http.server 和 SQLite；0 元付费逼出了 Gitee 中转和自建统计；国际链路的黑洞逼出了本地构建和原子发布。每一层被砍掉的「标准方案」，最后都变成了更便宜、更快、更可解释的自己方案。\n","permalink":"https://tuanzi-wow.cn/engineering/selfhosted-blog-postmortem/","summary":"\u003cp\u003e这台博客跑在一台最便宜的阿里云 ECS 上：2 vCPU、\u003cstrong\u003e实际可用内存 1.6 GiB\u003c/strong\u003e、固定 3 Mbps 带宽，约束是「除服务器本身外新增付费 0 元」。这篇文章复盘整个工程链路的四个关键决策，以及它们各自是怎么被现实打脸又修正的。\u003c/p\u003e","title":"复盘：在 1.6 GiB 的境内服务器上，把博客做成分钟级自愈系统"},{"content":"What is shown here is what idk before! 此处的应用场景是python解释器的交互模式之下: Do not laugh at me ! I\u0026rsquo;m REALLY a newbie!!!!\n1 2 3 python -c command arg python -m module arg //创建新New—Item name（真实的名称） ItemType type（具体的类型） 忘掉了的\n数字 除法运算 (/) 总是返回浮点数。如果要做 floor division 得到一个整数结果你可以使用 // 运算符；\nSomething New： Python 用 ** 运算符计算乘方 [1]： 交互模式下，上次输出的表达式会赋给变量 _。把 Python 当作计算器时，用该变量实现下一步计算更简单，例如：\n1 2 3 4 5 6 7 8 tax = 12.5 / 100 price = 100.50 price * tax 12.5625 price + _ 113.0625 round(_, 2) 113.06 除了 int 和 float，Python 还支持其他数字类型，例如 Decimal 或 Fraction。 Python 还内置支持 复数，后缀 j 或 J 用于表示虚数 (例如 3+5j)。(wow 复数，之前没有了解过这个表达形式)\n文本：有str类型表示 ‘’ 和 ‘’ “无差别 要标示引号本身，我们需要对它进行“转义”，即在前面加一个 \\。或者，我们也可以使用不同类型的引号: 如果要在str使用‘ ，我们可以整体上使用“ ”反之亦然；不过统一的\\貌似是一个更好的选择如果不希望前置 \\ 的字符转义成特殊字符，可以使用 原始字符串，在引号前添加 r 即可：\nCopy\n1 2 3 4 5 print(\u0026#39;C:\\this\\name\u0026#39;) # 这里 \\t 表示制表符，\\n 表示换行符 C: his ame print(r\u0026#39;C:\\this\\name\u0026#39;) # 请注意引号前的 r C:\\this\\name 原始字符串还有一个微妙的限制：一个原始字符串不能以奇数个 \\ 字符结束；请参阅 此 FAQ 条目 了解更多信息及绕过的办法。字符串字面值可以跨越多行。 一种做法是使用三重引号: \u0026quot;\u0026quot;\u0026quot;...\u0026quot;\u0026quot;\u0026quot; 或 '''...'''。 行结束符会自动包括在字符串中，但可以通过在行尾添加 \\ 来避免此行为。在下面的例子中，开头的换行符将不会被包括:\n1 2 3 4 5 6 7 8 print(\u0026#34;\u0026#34;\u0026#34;\\ Usage: thingy [OPTIONS] -h Display this usage message -H hostname Hostname to connect to \u0026#34;\u0026#34;\u0026#34;) Usage: thingy [OPTIONS] -h Display this usage message -H hostname Hostname to connect to 字符串字面值的自动拼接\n相邻的两个或多个 字符串字面值 （引号标注的字符）会自动合并：\n1 2 \u0026#39;Py\u0026#39; \u0026#39;thon\u0026#39; \u0026#39;Python\u0026#39; 拼接分隔开的长字符串时，这个功能特别实用：\n1 2 3 4 text = (\u0026#39;Put several strings within parentheses \u0026#39; \u0026#39;to have them joined together.\u0026#39;) text \u0026#39;Put several strings within parentheses to have them joined together.\u0026#39; 这项功能只能用于两个字面值，不能用于变量或表达式，合并变量\u0026amp;表达式要使用+：\nCopy\n1 2 3 4 5 6 7 8 9 10 11 prefix = \u0026#39;Py\u0026#39; prefix \u0026#39;thon\u0026#39; # 不能拼接变量和字符串字面值 File \u0026#34;\u0026lt;stdin\u0026gt;\u0026#34;, line 1 prefix \u0026#39;thon\u0026#39; ^^^^^^ SyntaxError: invalid syntax (\u0026#39;un\u0026#39; * 3) \u0026#39;ium\u0026#39; File \u0026#34;\u0026lt;stdin\u0026gt;\u0026#34;, line 1 (\u0026#39;un\u0026#39; * 3) \u0026#39;ium\u0026#39; ^^^^^ SyntaxError: invalid syntax 字符串的索引居然能够支持负数，从右边开始计数字符串支持 索引 （下标访问），第一个字符的索引是 0。单字符没有专用的类型，就是长度为一的字符串：\n1 2 3 4 5 word = \u0026#39;Python\u0026#39; word[0] # 0 号位的字符 \u0026#39;P\u0026#39; word[5] # 5 号位的字符 \u0026#39;n\u0026#39; 索引还支持负数，用负数索引时，从右边开始计数：\n1 2 3 4 5 6 word[-1] # 最后一个字符 \u0026#39;n\u0026#39; word[-2] # 倒数第二个字符 \u0026#39;o\u0026#39; word[-6] \u0026#39;P\u0026#39; 字符串的切：用于获取子字符串（substr来了说是）\n1 2 3 4 word[0:2] # 从 0 号位 (含) 到 2 号位 (不含) 的字符 \u0026#39;Py\u0026#39; word[2:5] # 从 2 号位 (含) 到 5 号位 (不含) 的字符 \u0026#39;tho\u0026#39; 切片索引的默认值：\n1 2 3 4 5 6 word[:2] # 从开头到 2 号位 (不含) 的字符 \u0026#39;Py\u0026#39; word[4:] # 从 4 号位 (含) 到末尾 \u0026#39;on\u0026#39; word[-2:] # 从倒数第二个 (含) 到末尾 \u0026#39;on\u0026#39; 注意，输出结果包含切片开始，但不包含切片结束。因此，s[:i] + s[i:] 总是等于 s： 都是如果结束索引是默认值，那么实际上还有一个终结符，我们可以理解为不包含这个终结符但是包含最后一位 可以换一个方式理解：切片是索引之间 直接通过索引访问单个位置可能会出现索引越界，但是切片会自动处理索引越界 Python的字符串不可修改，是immutable的（怎么和JAVA一样啊，恼！！！） 字符串长度获取：内置函数：len(); 我CHOVY，ntm讲字符串给我讲好了啊！！！ 下面是字符串的一些方法！！！ 自己去这里找 https://docs.python.org/zh-cn/3/library/stdtypes.html#textseq\n列表：mutable 支持索引，切片，合并 append（）；len（）：为切片赋值可以改变列表大小，甚至清空整个列表：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 letters = [\u0026#39;a\u0026#39;, \u0026#39;b\u0026#39;, \u0026#39;c\u0026#39;, \u0026#39;d\u0026#39;, \u0026#39;e\u0026#39;, \u0026#39;f\u0026#39;, \u0026#39;g\u0026#39;] letters [\u0026#39;a\u0026#39;, \u0026#39;b\u0026#39;, \u0026#39;c\u0026#39;, \u0026#39;d\u0026#39;, \u0026#39;e\u0026#39;, \u0026#39;f\u0026#39;, \u0026#39;g\u0026#39;] # 替换一些值 letters[2:5] = [\u0026#39;C\u0026#39;, \u0026#39;D\u0026#39;, \u0026#39;E\u0026#39;] letters [\u0026#39;a\u0026#39;, \u0026#39;b\u0026#39;, \u0026#39;C\u0026#39;, \u0026#39;D\u0026#39;, \u0026#39;E\u0026#39;, \u0026#39;f\u0026#39;, \u0026#39;g\u0026#39;] # 现在移除它们 letters[2:5] = [] letters [\u0026#39;a\u0026#39;, \u0026#39;b\u0026#39;, \u0026#39;f\u0026#39;, \u0026#39;g\u0026#39;] # 通过用一个空列表替代所有元素来清空列表 letters[:] = [] letters [] 嵌套列表（注意不是合并哦哦哦哦哦！！！！！！）\n1 2 3 4 5 6 7 8 9 a = [\u0026#39;a\u0026#39;, \u0026#39;b\u0026#39;, \u0026#39;c\u0026#39;] n = [1, 2, 3] x = [a, n] x [[\u0026#39;a\u0026#39;, \u0026#39;b\u0026#39;, \u0026#39;c\u0026#39;], [1, 2, 3]] x[0] [\u0026#39;a\u0026#39;, \u0026#39;b\u0026#39;, \u0026#39;c\u0026#39;] x[0][1] \u0026#39;b\u0026#39; 关键字参数 end 可以取消输出后面的换行, 或用另一个字符串结尾：\nCopy\n1 2 3 4 5 6 a, b = 0, 1 while a \u0026lt; 1000: print(a, end=\u0026#39;,\u0026#39;) a, b = b, a+b 0,1,1,2,3,5,8,13,21,34,55,89,144,233,377,610,987, ** 比 - 的优先级更高, 所以 -3**2 会被解释成 -(3**2) ，因此，结果是 -9。要避免这个问题，并且得到 9, 可以用 (-3)**2。\n[2]\n与其他语言不同，特殊字符如 \\n 在单引号 ('...') 和双引号 (\u0026quot;...\u0026quot;) 里的意义一样。 这两种引号唯一的区别是，不需要在单引号里转义双引号 \u0026quot; (但此时必须把单引号转义成 \\')，反之亦然。\n更多的控制流工具 range（）函数： 内置函数 range() 用于生成等差数列：\n1 2 3 4 5 6 7 8 for i in range(5): print(i) 0 1 2 3 4 生成的序列绝不会包括给定的终止值；range(10) 生成 10 个值——长度为 10 的序列的所有合法索引。range 可以不从 0 开始，且可以按给定的步长递增（即使是负数步长）：\n1 2 3 4 5 6 7 8 list(range(5, 10)) [5, 6, 7, 8, 9] list(range(0, 10, 3)) [0, 3, 6, 9] list(range(-10, -100, -30)) [-10, -40, -70] 要按索引迭代序列，可以组合使用 range() 和 len()：\n1 2 3 4 5 6 7 8 9 a = [\u0026#39;Mary\u0026#39;, \u0026#39;had\u0026#39;, \u0026#39;a\u0026#39;, \u0026#39;little\u0026#39;, \u0026#39;lamb\u0026#39;] for i in range(len(a)): print(i, a[i]) 0 Mary 1 had 2 a 3 little 4 lamb 不过大多数情况下 enumerate() 函数很方便，详见 循环的技巧。\n如果直接打印一个 range 会发生意想不到的事情：\n1 2 range(10) range(0, 10) range() 返回的对象在很多方面和列表的行为一样，但其实它和列表不一样。该对象只有在被迭代时才一个一个地返回所期望的列表项，并没有真正生成过一个含有全部项的列表，从而节省了空间。\n这种对象称为可迭代对象 iterable，适合作为需要获取一系列值的函数或程序构件的参数。for 语句就是这样的程序构件；以可迭代对象作为参数的函数例如 sum()：\n1 2 sum(range(4)) # 0 + 1 + 2 + 3 6 后续我们会看到更多返回可迭代对象并以可迭代对象作为参数的函数。 在 数据结构 一章中，我们将讨论 list() 的更多细节。\n列表的常见方法见 https://docs.python.org/zh-cn/3/tutorial/datastructures.html\n1 2 3 4 5 6 7 8 9 10 11 append（value）: extend(iterable): insert(index,value): remove(value): pop(index=-1): clear(): index(value,start,stop) count(value); sort(*,key=noe,reveres = False); reveres(): copy: 列表实现堆栈：后进先出 append();pop()\n列表实现队列：性能很差，最好使用collectiond.deque 列表推导式: 创建列表非常方便 筛选满足某些特定条件的元素/对全部元素进行操作\n嵌套的列表推导式 实际应用最好用内置函数替代复杂的流程函数\ndel：区分与pop 可接单个元素如a[0],切片，或者单个变量a\n元组和序列 元组：通过\u0026rsquo;,\u0026lsquo;隔开，通常使用（）标注，imutable，但是可以存储mutable的元素如列表 特别的单个元组：\n1 2 3 4 5 6 7 8 empty = () singleton = \u0026#39;hello\u0026#39;, # \u0026lt;-- 注意末尾的逗号 len(empty) 0 len(singleton) 1 singleton (\u0026#39;hello\u0026#39;,) 解包：\n1 x, y, z = t 集合：set 无序，去重 创建空集合只能用set（）；创建可以用{}和set（）； 集合推导式\n字典： 以key（键）为索引；任何不可变类型都可以作为key 特别的，元组仅包含（字符串，元组，数字）时也可以为key，但是如果它直接或者间接包含可变元素，则不可 键对值的集合，键唯一\n下标操作d[key]可能（空值时即key不存在）会导致KeyError，最好用get（），返回值或者none：\n1 2 3 4 list（d） ：获得所有key的列表，不包含value sort（），in dict（）：构造函数 字典推导式：关键字是比较简单的字符串时，直接用关键字参数指定键值对更便捷：\nCopy\n1 2 dict(sape=4139, guido=4127, jack=4098) {\u0026#39;sape\u0026#39;: 4139, \u0026#39;guido\u0026#39;: 4127, \u0026#39;jack\u0026#39;: 4098} 循环的技巧 同时获得字典的（key，value） ：item（）； 同时获得（index，value）：enumerate（）； 同时循环两个或者多个序列时：zip（）； 配置这个看的更明确\n1 2 3 4 5 6 7 8 questions = [\u0026#39;name\u0026#39;, \u0026#39;quest\u0026#39;, \u0026#39;favorite color\u0026#39;] answers = [\u0026#39;lancelot\u0026#39;, \u0026#39;the holy grail\u0026#39;, \u0026#39;blue\u0026#39;] for q, a in zip(questions, answers): print(\u0026#39;What is your {0}? It is {1}.\u0026#39;.format(q, a)) What is your name? It is lancelot. What is your quest? It is the holy grail. What is your favorite color? It is blue. 1 2 3 逆向：reverse（） 排序：sorted（） 去重：set（） 条件控制 1 2 3 4 in ；not in is ；not is 链式比较操作：a \u0026lt; b == c 优先级:not \u0026gt; and \u0026gt; or ","permalink":"https://tuanzi-wow.cn/python/python-day1/","summary":"\u003ch2 id=\"what-is-shown-here-is-what-idk-before\"\u003eWhat is shown here is what idk before!\u003c/h2\u003e\n\u003ch3 id=\"此处的应用场景是python解释器的交互模式之下\"\u003e此处的应用场景是python解释器的交互模式之下:\u003c/h3\u003e\n\u003cp\u003eDo not laugh at me ! I\u0026rsquo;m REALLY a newbie!!!!\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv class=\"chroma\"\u003e\n\u003ctable class=\"lntable\"\u003e\u003ctr\u003e\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"lnt\"\u003e1\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e2\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e3\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003epython\u003c/span\u003e \u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"n\"\u003ec\u003c/span\u003e \u003cspan class=\"n\"\u003ecommand\u003c/span\u003e \u003cspan class=\"n\"\u003earg\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003epython\u003c/span\u003e \u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"n\"\u003em\u003c/span\u003e \u003cspan class=\"n\"\u003emodule\u003c/span\u003e \u003cspan class=\"n\"\u003earg\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"o\"\u003e//\u003c/span\u003e\u003cspan class=\"n\"\u003e创建新New\u003c/span\u003e\u003cspan class=\"err\"\u003e—\u003c/span\u003e\u003cspan class=\"n\"\u003eItem\u003c/span\u003e \u003cspan class=\"n\"\u003ename\u003c/span\u003e\u003cspan class=\"err\"\u003e（\u003c/span\u003e\u003cspan class=\"n\"\u003e真实的名称\u003c/span\u003e\u003cspan class=\"err\"\u003e）\u003c/span\u003e \u003cspan class=\"n\"\u003eItemType\u003c/span\u003e \u003cspan class=\"nb\"\u003etype\u003c/span\u003e\u003cspan class=\"err\"\u003e（\u003c/span\u003e\u003cspan class=\"n\"\u003e具体的类型\u003c/span\u003e\u003cspan class=\"err\"\u003e）\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003e忘掉了的\u003c/p\u003e\n\u003ch4 id=\"数字\"\u003e数字\u003c/h4\u003e\n\u003cp\u003e除法运算 (\u003ccode\u003e/\u003c/code\u003e) 总是返回浮点数。\u003cstrong\u003e如果要做 \u003ca href=\"https://docs.python.org/zh-cn/3/glossary.html#term-floor-division\"\u003efloor division\u003c/a\u003e 得到一个整数结果你可以使用 \u003ccode\u003e//\u003c/code\u003e 运算符；\u003c/strong\u003e\u003c/p\u003e","title":"DAY1 · Python 交互模式随手记"},{"content":"OpenAI是如何使用Codex: 用例1:代码理解 识别核心逻辑;梳理服务和板块之间的关系;追踪数据在系统中的流转;识别架构模式和文档确实的部分\n用例2:重构和迁移: 跨文件执行修改操作;拆分模块\n用例3:性能优化: 用于识别并解决性能瓶颈问题\n用例4:提高测试覆盖率 帮助编写测试:边界情况和异常路径\n用例5:提高开发速度 提高开发前期和收尾阶段的效率\n用例6:保持专注高效 帮助在日常零碎/频繁被打断的情况下保持任务专注从而提高效率\n用例7:探索和创意构建 用于开发性工作:给出多种思路,探索陌生开发模式;对各类假设进行验证\n最佳实践: 提供清晰的框架,上下文和迭代空间\n从\u0026quot;询问模式\u0026quot;开始: 在询问模式下获取可实施方案-\u0026gt;在代码模式下将其作为参考依据\n持续改进的Codex的开发环境: 配置环境变量,启动脚本以及网络权限\n像GitHub Issue一样组织提示: 按照在PR或Issue中描述代码变更的口吻编写提示\n将Codex任务队列用作简单的任务清单: 临时新建任务纪录零碎想法,未完成工作或者临时小的更改\n使用AGENTS.md文件提供持续上下文: 内容:该文件通常包含仅凭代码无法让 Codex 自行推断的命名规范、业务逻辑、特殊规则以及依赖关系\n使用Best of N 提高输出效果: 单个任务一次性生成多份结果,择优选用\n","permalink":"https://tuanzi-wow.cn/vibe-coding/vibe-coding-how-openai-uses-codex/","summary":"\u003ch2 id=\"openai是如何使用codex\"\u003eOpenAI是如何使用Codex:\u003c/h2\u003e\n\u003ch3 id=\"用例1代码理解\"\u003e用例1:代码理解\u003c/h3\u003e\n\u003cp\u003e识别核心逻辑;梳理服务和板块之间的关系;追踪数据在系统中的流转;识别架构模式和文档确实的部分\u003c/p\u003e\n\u003ch3 id=\"用例2重构和迁移\"\u003e用例2:重构和迁移:\u003c/h3\u003e\n\u003cp\u003e跨文件执行修改操作;拆分模块\u003c/p\u003e\n\u003ch3 id=\"用例3性能优化\"\u003e用例3:性能优化:\u003c/h3\u003e\n\u003cp\u003e用于识别并解决性能瓶颈问题\u003c/p\u003e","title":"OpenAI 是如何使用 Codex 的"},{"content":"Personalize GPT set preference and carry useful context across chats:\nChoose a personality: Friendly, pragmatic,or None\nAdd custom instructions: In your global AGENTS.md\nCarry context forward with memories: Memories are separate from required project guidance. Keep instructions that must always apply in AGENTS.md or checked-in project documentation.\nAdd recent activity with Computer History Review what Computer History includes before enabling it\nManage personalization: pen Settings to update your personality, custom instructions, memories, and other available personalization controls. See ChatGPT desktop app settings for an overview of everyday preferences.\n","permalink":"https://tuanzi-wow.cn/codex/codex-personalize-chatgpt/","summary":"\u003ch2 id=\"personalize-gpt\"\u003ePersonalize GPT\u003c/h2\u003e\n\u003cp\u003eset preference and carry useful context across chats:\u003c/p\u003e\n\u003ch2 id=\"choose-a-personality\"\u003eChoose a personality:\u003c/h2\u003e\n\u003cp\u003eFriendly, pragmatic,or None\u003c/p\u003e\n\u003ch2 id=\"add-custom-instructions\"\u003eAdd custom instructions:\u003c/h2\u003e\n\u003cp\u003eIn your global \u003cstrong\u003eAGENTS.md\u003c/strong\u003e\u003c/p\u003e\n\u003ch2 id=\"carry-context-forward-with-memories\"\u003eCarry context forward with memories:\u003c/h2\u003e\n\u003cp\u003eMemories are separate from required project guidance. Keep instructions that must always apply in \u003ccode\u003eAGENTS.md\u003c/code\u003e or checked-in project documentation.\u003c/p\u003e\n\u003ch2 id=\"add-recent-activity-with-computer-history\"\u003eAdd recent activity with Computer History\u003c/h2\u003e\n\u003cp\u003eReview what Computer History includes before enabling it\u003c/p\u003e\n\u003ch2 id=\"manage--personalization\"\u003eManage  personalization:\u003c/h2\u003e\n\u003cp\u003epen \u003ca href=\"#ZgotmplZ\"\u003e\u003cstrong\u003eSettings\u003c/strong\u003e\u003c/a\u003e to update your personality, custom instructions, memories, and other available personalization controls. See \u003ca href=\"https://learn.chatgpt.com/docs/reference/settings\"\u003eChatGPT desktop app settings\u003c/a\u003e for an overview of everyday preferences.\u003c/p\u003e","title":"个性化 ChatGPT"},{"content":"Compaction Server-side context compaction for managing long conversations that approaching context window limits\n1 Server-side compaction is the recommended strategy for managing context in long-running conversations and agentic workflows. It handles context management automatically, without client-side summarization code. as a conversation grows, response quality degrades,Compaction extends the effective context length for long-running conversations and tasks compaction replaces older content with a concise summary.\nThis is ideal for:\nChat-based, multi-turn conversations where you want users to use one chat for a long period of time Task-oriented prompts that require a lot of follow-up work (often tool use) that might exceed the context window How compaction works four steps:\nDetects when input tokens reach your specified trigger threshold. Generates a summary of the current conversation. Creates a compaction block containing the summary. Continues the response with the compacted context. Basic usage Enable compaction by adding the compact_20260112 strategy to context_management.edits in your Messages API request.\nParameters Parameter Type Default Description type string Required Must be \u0026quot;compact_20260112\u0026quot; trigger object {\u0026quot;type\u0026quot;: \u0026quot;input_tokens\u0026quot;, \u0026quot;value\u0026quot;: 150000} When to trigger compaction. input_tokens is the only supported trigger type. value must be at least 50,000 tokens. pause_after_compaction boolean false Whether to pause after generating the compaction summary instructions string null Custom summarization prompt. Completely replaces the default prompt when provided. ps: type must be \u0026ldquo;compact_20260112\u0026rdquo;: if u use Claude API, for sure; but if u def your own compaction function, it change use other value\nTrigger configuration 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 client = anthropic.Anthropic() messages = [{\u0026#34;role\u0026#34;: \u0026#34;user\u0026#34;, \u0026#34;content\u0026#34;: \u0026#34;Hello, Claude\u0026#34;}] response = client.beta.messages.create( betas=[\u0026#34;compact-2026-01-12\u0026#34;], model=\u0026#34;claude-opus-5\u0026#34;, max_tokens=4096, messages=messages, context_management={ \u0026#34;edits\u0026#34;: [ { \u0026#34;type\u0026#34;: \u0026#34;compact_20260112\u0026#34;, \u0026#34;trigger\u0026#34;: {\u0026#34;type\u0026#34;: \u0026#34;input_tokens\u0026#34;, \u0026#34;value\u0026#34;: 150000}, } ] }, ) Custom summarization instructions /compaction default instructions:\n1 You have written a partial transcript for the initial task above. Please write a summary of the transcript. The purpose of this summary is to provide continuity so you can continue to make progress towards solving the task in a future context, where the raw history above may not be accessible and will be replaced with this summary. Write down anything that would be helpful, including the state, next steps, learnings etc. You must wrap your summary in a \u0026lt;summary\u0026gt;\u0026lt;/summary\u0026gt; block. Custom instructions don\u0026rsquo;t supplement the default prompt. They replace it completely\nOn Claude Fable 5.1 and Claude Mythos 5.1, a request with custom instructions summarizes from the visible conversation only: earlier thinking blocks are not part of the summarizer\u0026rsquo;s input.\nPausing after compaction Use pause_after_compaction to pause the API after generating the compaction summary. This allows you to add additional content blocks\n1 2 3 context_management={ \u0026#34;edits\u0026#34;: [{\u0026#34;type\u0026#34;: \u0026#34;compact_20260112\u0026#34;, \u0026#34;pause_after_compaction\u0026#34;: True}] } Enforce a total token budget When a model works on long tasks with many tool-use iterations, total token consumption can grow significantly. You can combine pause_after_compaction with a compaction counter to estimate cumulative usage and gracefully wrap up the task once a budget is reached.\nThis example appears in the SDK languages only: its value is the budget-tracking logic around the request. The raw request combines the trigger from Trigger configuration with pause_after_compaction from Pausing after compaction.\n1 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 client = anthropic.Anthropic() messages = [{\u0026#34;role\u0026#34;: \u0026#34;user\u0026#34;, \u0026#34;content\u0026#34;: \u0026#34;Hello, Claude\u0026#34;}] TRIGGER_THRESHOLD = 100_000 TOTAL_TOKEN_BUDGET = 3_000_000 n_compactions = 0 response = client.beta.messages.create( betas=[\u0026#34;compact-2026-01-12\u0026#34;], model=\u0026#34;claude-opus-5\u0026#34;, max_tokens=4096, messages=messages, context_management={ \u0026#34;edits\u0026#34;: [ { \u0026#34;type\u0026#34;: \u0026#34;compact_20260112\u0026#34;, \u0026#34;trigger\u0026#34;: {\u0026#34;type\u0026#34;: \u0026#34;input_tokens\u0026#34;, \u0026#34;value\u0026#34;: TRIGGER_THRESHOLD}, \u0026#34;pause_after_compaction\u0026#34;: True, } ] }, ) if response.stop_reason == \u0026#34;compaction\u0026#34;: n_compactions += 1 messages.append({\u0026#34;role\u0026#34;: \u0026#34;assistant\u0026#34;, \u0026#34;content\u0026#34;: response.content}) # Estimate total tokens consumed; prompt wrap-up if over budget if n_compactions * TRIGGER_THRESHOLD \u0026gt;= TOTAL_TOKEN_BUDGET: messages.append( { \u0026#34;role\u0026#34;: \u0026#34;user\u0026#34;, \u0026#34;content\u0026#34;: \u0026#34;Please wrap up your current work and summarize the final state.\u0026#34;, } ) pause能够允许用户能够塞入自己的判断逻辑,防止超过总预算但是task未停止\nWorking with compaction blocks When compaction is triggered, the API returns a compaction block at the start of the assistant response. The new compaction block will replace the old one\nPassing compaction blocks back You must pass the compaction block back to the API on subsequent requests to continue the conversation with the shortened prompt.\nWhen the API receives a compaction block, all content blocks before it are ignored. You can either:\nKeep the original messages in your list and let the API handle removing the compacted content Manually drop the compacted messages and only include the compaction block onwards Streaming The compaction block streams differently from text blocks. You receive a content_block_start event, followed by a single content_block_delta with the complete summary content (no intermediate streaming), and then a content_block_stop event.\ncontent_block_start -\u0026gt; content_block_delta -\u0026gt; content_block_stop\ncontent_block_delta has the complete summary content\nPrompt caching Compaction works well with prompt caching. You can add a cache_control breakpoint on compaction blocks to cache the summarized content.\nMaximining cache hits with system prompts 系统提示词的缓存和压缩新写入的内容会分开,这样写入的就只有压缩后的内容,而系统提示词的缓存不会失效,实现的方法是在你的系统提示词的后面打一个断点\nWhen compaction occurs, the summary becomes new content that needs to be written to the cache. Without additional cache breakpoints, this would also invalidate any cached system prompt, requiring it to be re-cached along with the compaction summary.\nTo maximize cache hit rates, add a cache_control breakpoint at the end of your system prompt. This keeps the system prompt cached separately from the conversation, so when compaction occurs:\nThe system prompt cache remains valid and is read from cache Only the compaction summary needs to be written as a new cache entry his keeps long system prompts cached across multiple compaction events throughout a conversation.\nUnderstanding usage Compaction requires an additional sampling step, which contributes to rate limits and billing. The API returns detailed usage information in the response:\nOutput\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 { \u0026#34;usage\u0026#34;: { \u0026#34;input_tokens\u0026#34;: 23000, \u0026#34;output_tokens\u0026#34;: 1000, \u0026#34;iterations\u0026#34;: [ { \u0026#34;type\u0026#34;: \u0026#34;compaction\u0026#34;, \u0026#34;input_tokens\u0026#34;: 180000, \u0026#34;output_tokens\u0026#34;: 3500 }, { \u0026#34;type\u0026#34;: \u0026#34;message\u0026#34;, \u0026#34;input_tokens\u0026#34;: 23000, \u0026#34;output_tokens\u0026#34;: 1000 } ] } } The iterations array shows usage for each sampling iteration. When compaction occurs, you\u0026rsquo;ll see a compaction iteration followed by the main message iteration. The top-level input_tokens and output_tokens match the message iteration exactly in this example because there is only one non-compaction iteration. The final iteration\u0026rsquo;s token counts reflect the effective context size after compaction.\ninerations数组中comapaction部分记录了压缩之前的上下文大小和压缩后的上下文大小,上面的usage和message相同是因为: 上述只有一次非compaction迭代\n1 2 3 The top-level input_tokens and output_tokens do not include compaction iteration usage. They reflect the sum of all non-compaction iterations. To calculate total tokens consumed and billed for a request, sum across all entries in the usage.iterations array. If you previously relied on usage.input_tokens and usage.output_tokens for cost tracking or auditing, you\u0026#39;ll need to update your tracking logic to aggregate across usage.iterations when compaction is enabled. With the compaction beta enabled, every response includes usage.iterations, even if no compaction occurred. A compaction entry appears only when a new compaction is triggered during the request. Re-applying a previous compaction block incurs no additional compaction cost, and the top-level usage fields remain accurate in that case. 顶端的input_token和output_token不包含压缩迭代所产生的使用量,反应的是非压缩迭代的总和 要计算一次实际请求用量,要对usage.iteration中数组中的所有条目求和依赖usage.input_token和usage.output.token来进行成本统计的方式需要转变逻辑结构,转化为对于usage.interation中的每一项进行汇总 在启用compaction Beta之后,每个响应都会柏寒usage.interation,即使没有发生上下文压缩 但是只有出现了compaction,才会在interation中出现compaction条目 重新应用原来已经存在的compaction block不会产生额外的上下文压缩费用\nCombing With other Features Server tools When using server tools (such as web search), the compaction trigger is checked at the start of each sampling iteration. Compaction might occur multiple times within a single request depending on your trigger threshold and the amount of output generated. 系统会在每个迭代之前检查是否需要压缩,这取决于你的设置和产出的数量\nToken counting The token counting endpoint (/v1/messages/count_tokens) applies existing compaction blocks in your prompt but does not trigger new compactions. Use it to check your effective token count after previous compactions 使用现存的compaction块而不会激活compaction,所哟不会产生新的上下文费用\nCurrent limitations Same model for summarization: The model specified in your request is used for summarization. There is no option to use a different (for example, cheaper) model for the summary. 有点变态的是,Claudecode会给每一个模型都开新的缓存\nCompaction might fail when tools are defined: When your request includes tools, the model occasionally calls a tool during the internal summarization step instead of writing a summary. When this occurs, the response contains a compaction block with content: null. To prevent this, set instructions to a prompt that explicitly tells the model not to call tools, for example: 当你的请求中包含tools是,有时候压缩会因为调用工具而失败 解决方法:在Instruction中显式地声明不调用工具 ","permalink":"https://tuanzi-wow.cn/%E4%B8%8A%E4%B8%8B%E6%96%87%E5%B7%A5%E7%A8%8B/claude_context_management/context-compaction/","summary":"\u003ch1 id=\"compaction\"\u003eCompaction\u003c/h1\u003e\n\u003cp\u003eServer-side context compaction for managing long conversations that approaching context window limits\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv class=\"chroma\"\u003e\n\u003ctable class=\"lntable\"\u003e\u003ctr\u003e\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"lnt\"\u003e1\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eServer-side compaction is the recommended strategy for managing context in long-running conversations and agentic workflows. It handles context management automatically, without client-side summarization code.\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003eas a conversation grows, response quality degrades,Compaction extends the effective context length for long-running conversations and tasks\ncompaction replaces older content with a concise summary.\u003c/p\u003e\n\u003cp\u003eThis is ideal for:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eChat-based, multi-turn conversations where you want users to use one chat for a long period of time\u003c/li\u003e\n\u003cli\u003eTask-oriented prompts that require a lot of follow-up work (often tool use) that might exceed the context window\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"how-compaction-works\"\u003eHow compaction works\u003c/h2\u003e\n\u003cp\u003efour steps:\u003c/p\u003e","title":"Context Management · Compaction"},{"content":"1.矩阵置零 题目描述 给定一个 *m* x *n* 的矩阵，如果一个元素为 0 ，则将其所在行和列的所有元素都设为 0 。请使用 原地 算法**。**\n示例 1：\n1 2 输入：matrix = [[1,1,1],[1,0,1],[1,1,1]] 输出：[[1,0,1],[0,0,0],[1,0,1]] 示例 2：\n1 2 输入：matrix = [[0,1,2,0],[3,4,5,2],[1,3,1,5]] 输出：[[0,0,0,0],[0,4,5,0],[0,3,1,0]] 题解： O(MN):\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution: def setZeroes(self, matrix: List[List[int]]) -\u0026gt; None: \u0026#34;\u0026#34;\u0026#34; Do not return anything, modify matrix in-place instead. \u0026#34;\u0026#34;\u0026#34; m = len(matrix) n = len(matrix[0]) ans = [] for x in range(m): for y in range(n): if matrix[x][y] == 0: ans.append((x,y)) for ele in ans: for y in range(n): matrix[ele[0]][y] = 0 for x in range(m): matrix[x][ele[1]] = 0 方法二：第一行和列作为标记位：挺简单的，自己看\n1 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 class Solution: def setZeroes(self, matrix: List[List[int]]) -\u0026gt; None: \u0026#34;\u0026#34;\u0026#34; Do not return anything, modify matrix in-place instead. \u0026#34;\u0026#34;\u0026#34; row = len(matrix) col = len(matrix[0]) row0_flag = False col0_flag = False # 找第一行是否有0 for j in range(col): if matrix[0][j] == 0: row0_flag = True break # 第一列是否有0 for i in range(row): if matrix[i][0] == 0: col0_flag = True break # 把第一行或者第一列作为 标志位 for i in range(1, row): for j in range(1, col): if matrix[i][j] == 0: matrix[i][0] = matrix[0][j] = 0 #print(matrix) # 置0 for i in range(1, row): for j in range(1, col): if matrix[i][0] == 0 or matrix[0][j] == 0: matrix[i][j] = 0 if row0_flag: for j in range(col): matrix[0][j] = 0 if col0_flag: for i in range(row): matrix[i][0] = 0 2.螺旋矩阵 题目描述 给你一个 m 行 n 列的矩阵 matrix ，请按照 顺时针螺旋顺序 ，返回矩阵中的所有元素。\n示例 1：\n1 2 输入：matrix = [[1,2,3],[4,5,6],[7,8,9]] 输出：[1,2,3,6,9,8,7,4,5] 示例 2：\n1 2 输入：matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]] 输出：[1,2,3,4,8,12,11,10,9,5,6,7] 提示：\nm == matrix.length n == matrix[i].length 1 \u0026lt;= m, n \u0026lt;= 10 -100 \u0026lt;= matrix[i][j] \u0026lt;= 100 题解： 我说实话，这道题是目前见过最有意思的了 方向数组，取余转向，m,n = n,m-1这几个都挺有意思的\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 DIRS = (0, 1), (1, 0), (0, -1), (-1, 0) # 右下左上 class Solution: def spiralOrder(self, matrix: List[List[int]]) -\u0026gt; List[int]: m, n = len(matrix), len(matrix[0]) size = m * n ans = [] i, j, di = 0, -1, 0 # 从 (0, -1) 开始 while len(ans) \u0026lt; size: dx, dy = DIRS[di] for _ in range(n): # 走 n 步（注意 n 会减少） i += dx j += dy # 先走一步 ans.append(matrix[i][j]) # 再加入答案 di = (di + 1) % 4 # 右转 90° m, n = n, m - 1 # 减少后面的循环次数（步数） return ans 3.旋转图像 题目描述：给定一个 n × n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。\n你必须在** 原地** 旋转图像，这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。\n示例 1：\n1 2 输入：matrix = [[1,2,3],[4,5,6],[7,8,9]] 输出：[[7,4,1],[8,5,2],[9,6,3]] 示例 2：\n1 2 输入：matrix = [[5,1,9,11],[2,4,8,10],[13,3,6,7],[15,14,12,16]] 输出：[[15,13,2,5],[14,3,4,1],[12,6,8,9],[16,7,10,11]] 题解： IM SO FUCKING SMART！！！！\n1 2 3 4 5 6 7 8 9 10 11 12 13 class Solution: def rotate(self, matrix: List[List[int]]) -\u0026gt; None: \u0026#34;\u0026#34;\u0026#34; Do not return anything, modify matrix in-place instead. \u0026#34;\u0026#34;\u0026#34; n = len(matrix) ans = [[0] * n for _ in range(n)] for i in range(n): for j in range(i+1,n):注意这个i+1啊，只交换一半，不然就交换回去了 matrix[i][j],matrix[j][i] = matrix[j][i],matrix[i][j] for row in matrix: row.reverse() 4.搜索二维矩阵： 题目描述： 编写一个高效的算法来搜索 *m* x *n* 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性：\n每行的元素从左到右升序排列。 每列的元素从上到下升序排列。 题解 直接暴力了啊\n1 2 3 4 5 6 7 8 9 10 class Solution: def searchMatrix(self, matrix: List[List[int]], target: int) -\u0026gt; bool: m = len(matrix) n = len(matrix[0]) for x in range(m): for y in range(n): if matrix[x][y] == target: return True return False 或者改进逻辑：从右上角(左下角也可以）开始寻找，小的在左，大的在下，保持单一方向单一逻辑\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Solution: def searchMatrix(self, matrix: List[List[int]], target: int) -\u0026gt; bool: m,n = len(matrix),len(matrix[0]) row,col = 0,n-1 while row \u0026lt; m and col \u0026gt;= 0: if matrix[row][col] == target: return True elif matrix[row][col] \u0026gt; target: col -= 1 else: row += 1 return False ","permalink":"https://tuanzi-wow.cn/leetcode%E5%88%B7%E9%A2%98%E8%AE%B0%E5%BD%95/hot100/hot100-matrix/","summary":"\u003ch3 id=\"1矩阵置零\"\u003e1.矩阵置零\u003c/h3\u003e\n\u003ch4 id=\"题目描述\"\u003e题目描述\u003c/h4\u003e\n\u003cp\u003e给定一个 \u003ccode\u003e*m* x *n*\u003c/code\u003e 的矩阵，如果一个元素为 \u003cstrong\u003e0\u003c/strong\u003e ，则将其所在行和列的所有元素都设为 \u003cstrong\u003e0\u003c/strong\u003e 。请使用 \u003cstrong\u003e\u003ca href=\"http://baike.baidu.com/item/%E5%8E%9F%E5%9C%B0%E7%AE%97%E6%B3%95\"\u003e原地\u003c/a\u003e\u003c/strong\u003e 算法**。**\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e示例 1：\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"img\" loading=\"lazy\" src=\"https://assets.leetcode.com/uploads/2020/08/17/mat1.jpg\"\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv class=\"chroma\"\u003e\n\u003ctable class=\"lntable\"\u003e\u003ctr\u003e\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"lnt\"\u003e1\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e2\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e输入：matrix = [[1,1,1],[1,0,1],[1,1,1]]\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e输出：[[1,0,1],[0,0,0],[1,0,1]]\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003e\u003cstrong\u003e示例 2：\u003c/strong\u003e\u003c/p\u003e","title":"HOT100 · 矩阵"},{"content":"提示技术 零样本提示 没有向模型提供示例\n少样本提示(few-shot) 提供少量示例以激发上下文学习\n限制:处理复杂任务 链式思考提示(Chain-of-Thought Prompting) CoT提示通过中间推理步骤实现了复杂的推理能力 可以与few-shot结合\n零样本COT提示 基本上涉及\u0026quot;将让我们逐步思考\u0026quot;添加到原始提示中\n自动思维链 当思维链提示时,需要手工制作有效且多样化的例子,可能会导致次优解决方案\n消除人工的方法:\nAuto-CoT主要由两个阶段组成:\n阶段一:问题聚类:将给定问题分为几个聚类 阶段二:演示抽样:从每组数组中选择一个具有代表性的问题,并且使用简单启发式的Zero-Shot-Cot生成其推理链 自我一致性 旨在\u0026quot;替换链式思维提示中使用的天真贪婪解码方法\u0026quot; 通过少量CoT采样多种不同的推理路径,并使用生成结构选择最一致的答案 核心:Prompt中提供多种推理链,选择一致的答案\n生成知识提示 生成知识作为提示的一部分\nPrompt Chain 将任务拆解为许多子任务,确定子任务之后,将子任务的提示词提供给LLM,得到的结果作为提示作为新的提示词的一部分 链式提示(prompt chain):一个任务被分解为多个子任务,根据子任务创建一系列提示操作\n提高了控制性和可靠性,可容易定位模型中的问题,并分析改进需要提高的不同阶段的性能\n示例文档问答中的链式提示 提示链可以用于不同的场景，这些场景可能涉及多个操作或转换。例如，LLM 的一个常见用途是根据大型文本文档回答问题。想要更好阅读大文本文档，可以设计两个不同的提示，第一个提示负责提取相关引文以回答问题，第二个提示则以引文和原始文档为输入来回答给定的问题。换句话说，可以创建两个不同的提示来执行根据文档回答问题的任务。\n下面的第一个提示根据问题从文档中提取相关引文。请注意，为了简化，我们为文档添加了一个占位符{{文档}}。要测试此提示，您可以从维基百科复制并粘贴一篇文章，例如这个关于提示工程的页面。由于此任务使用了较长的上下文，我们使用了 OpenAI 的 gpt-4-1106-preview 模型。您也可以将此提示与其他长上下文 LLM（如 Claude）一起使用。\n提示 1:\n1 你是一个很有帮助的助手。你的任务是根据文档回答问题。第一步是从文档中提取与问题相关的引文，由####分隔。请使用\u0026lt;quotes\u0026gt;\u0026lt;/quotes\u0026gt;输出引文列表。如果没有找到相关引文，请回应“未找到相关引文！”。####{{文档}}#### 这是整个提示的截图，包括通过 user 角色传递的问题。\n提示 1 的输出：\n1 \u0026lt;quotes\u0026gt;- Chain-of-thought (CoT) prompting[27]- Generated knowledge prompting[37]- Least-to-most prompting[38]- Self-consistency decoding[39]- Complexity-based prompting[41]- Self-refine[42]- Tree-of-thought prompting[43]- Maieutic prompting[45]- Directional-stimulus prompting[46]- Textual inversion and embeddings[59]- Using gradient descent to search for prompts[61][62][63][64]- Prompt injection[65][66][67]\u0026lt;/quotes\u0026gt; 在第一个提示中返回的引文现在可以用作下面第二个提示的输入。您可以对这些引文进行清理，比如移除引用标志。可以在提示链中新建另一个提示来移除或使用这些引用标志，但现在您可以忽略这些。然后，第二个提示接收由第一个提示提取的相关引文，并根据文档和这些提取的引文生成一个有帮助的回答。第二个提示可以是以下内容：\n提示 2：\n1 根据从文档中提取的相关引文（由\u0026lt;quotes\u0026gt;\u0026lt;/quotes\u0026gt;分隔）和原始文档（由####分隔），请构建对问题的回答。请确保答案准确、语气友好且有帮助。####{{文档}}####\u0026lt;quotes\u0026gt;- Chain-of-thought (CoT) prompting[27]- Generated knowledge prompting[37]- Least-to-most prompting[38]- Self-consistency decoding[39]- Complexity-based prompting[41]- Self-refine[42]- Tree-of-thought prompting[43]- Maieutic prompting[45]- Directional-stimulus prompting[46]- Textual inversion and embeddings[59]- Using gradient descent to search for prompts[61][62][63][64]- Prompt injection[65][66][67]\u0026lt;/quotes\u0026gt; 提示 2 的输出：\n1 文档中提到的提示技术包括：1. Chain-of-thought (CoT) prompting[27]2. Generated knowledge prompting[37]3. Least-to-most prompting[38]4. Self-consistency decoding[39]5. Complexity-based prompting[41]6. Self-refine[42]7. Tree-of-thought prompting[43]8. Maieutic prompting[45]9. Directional-stimulus prompting[46]10. Textual inversion and embeddings[59]11. Using gradient descent to search for prompts[61][62][63][64]12. Prompt injection[65][66][67]每一种技术都采用独特的策略来增强或明确与大型语言模型的互动，以产生期望的结果。 如您所见，简化并创建提示链是一种有用的提示方法，其中响应需要经过多个操作或转换。作为练习，您可以自由设计一个提示，它会在将响应作为最终回应发送给应用程序用户之前，移除响应中的引用标志（例如，[27]）。\n您还可以在这份文档中找到更多关于提示链的示例，这些示例利用了 Claude LLM。我们的示例灵感来源于他们，并采用了他们的示例。\n思维树(ToT) Tree of Thoughts，ToT:基于思维链提示进行了总结,引导LM把思维作为中间步骤解决同通用问题\nToT 维护着一棵思维树，思维由连贯的语言序列表示，这个序列就是解决问题的中间步骤\n检索增强生成(RAG) 完成复杂的知识密集型的任务\n将一个信息检索组件和文本生成模型结合在一起 接受输入并检索出一组相关/支撑的文档,并给出文档的来源,这些文档作为上下文和输入的原始提示词组合,送给文本生成器得到最终的输出\n自动推理并使用工具(ART) 交替使用Cot提示和工具被证明为一种强大且稳健的方法,但是这些方法需要针对特定任务手写规范还要精心编写交替使用生成模型和工具的脚本\nART(Automatic Reasoning and Tool-use)的工作原理如下:\n接受一个新任务时,从任务库选择多部推理和使用工具的示范 在测试中,调用外部工具时,先暂停生成,将工具输出整合后继续生成 自动提示工程师(APE) 用于自动指令生成和选择的框架 指令生成问题被构建为自然语言合成问题，使用 LLMs 作为黑盒优化问题的解决方案来生成和搜索候选解。 第一步涉及一个大型语言模型（作为推理模型），该模型接收输出演示以生成任务的指令候选项。这些候选解将指导搜索过程。使用目标模型执行指令，然后根据计算的评估分数选择最合适的指令\nActive-Prompt 第一步是使用或不使用少量 CoT 示例查询 LLM。对一组训练问题生成 k 个可能的答案。基于 k 个答案计算不确定度度量（使用不一致性）。选择最不确定的问题由人类进行注释。然后使用新的注释范例来推断每个问题 人类干预\n方向性刺激提示 训练了一个可调节的策略 LM 来生成刺激/提示。越来越多地使用RL来优化 LLM。\nPAL(程序辅助语言框架) 不通过自由形式我呢本来获取解决方法,将解决步骤卸载到类似于Python解释器的编程运行时中\nReAct框架 以交错的方式生成推理轨迹和任务特定操作\n如何运作的 将推理和行为与LLMs相结合通用的范例 ReAct提示LLMs为任务生成口头轨迹和操作,使得系统执行动态推理来创建,维护和调整操作计划,同时还支持与外部环境的交互,将额外信息合并到推理中\nReAct提示 第一步:从训练集中选择案例,并组成ReAct格式的轨迹下面是一个 ReAct 提示的示例(摘自论文，为简单起见缩短为一个示例):\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 问题 科罗拉多造山带东部区域延伸到的区域的海拔范围是多少? 思考 1 我需要搜索科罗拉多造山带，找到科罗拉多造山带东段延伸的区域，然后找到该区域的海拔范围。 操作 1 搜索 [科罗拉多造山带] 观察 1 科罗拉多造山带是科罗拉多及其周边地区造山运动的一段。 思考 2 它没有提到东部地区。所以我需要查一下东部地区。 操作 2 查找 [东部地区] 观察 2 (结果 1 / 1) 东部区域延伸至高平原，称为中原造山带。 思考 3 科罗拉多造山运动的东部区域延伸到高平原。所以我需要搜索高平原，找到它的海拔范围。 操作 3 搜索 [高平原] 观察 3 高平原指的是两个截然不同的陆地区域之一。 思考 4 我需要搜索高平原（美国） 操作 4 搜索 [高平原（美国）] 观察 4 高平原是大平原的一个分区。从东到西，高平原的海拔从 1800 到 7000 英尺 (550 到 2130 米) 不等。[3] 思考 5 高平原海拔高度从 1800 到 7000 英尺，所以答案是 1800 到 7000 英尺。 操作 5 结束 [1800 到 7000 英尺] ... 多思考-操作-观察步骤用于任务-解决轨迹\n总而言之:\nCoT 存在事实幻觉的问题 ReAct 的结构性约束降低了它在制定推理步骤方面的灵活性 ReAct 在很大程度上依赖于它正在检索的信息;非信息性搜索结果阻碍了模型推理，并导致难以恢复和重新形成思想 长链 ReAct 的使用 下面是 ReAct 提示方法在实践中如何工作的高阶示例。我们将在 LLM 和 长链 中使用OpenAI，因为它已经具有内置功能，可以利用 ReAct 框架构建代理，这些代理能够结合 LLM 和其他多种工具的功能来执行任务。\n首先，让我们安装并导入必要的库:\n1 %%capture# 更新或安装必要的库!pip install --upgrade openai!pip install --upgrade langchain!pip install --upgrade python-dotenv!pip install google-search-results # 引入库import openaiimport osfrom langchain.llms import OpenAIfrom langchain.agents import load_toolsfrom langchain.agents import initialize_agentfrom dotenv import load_dotenvload_dotenv() # 载入 API keys; 如果没有，你需要先获取。 os.environ[\u0026#34;OPENAI_API_KEY\u0026#34;] = os.getenv(\u0026#34;OPENAI_API_KEY\u0026#34;)os.environ[\u0026#34;SERPER_API_KEY\u0026#34;] = os.getenv(\u0026#34;SERPER_API_KEY\u0026#34;) 现在我们可以配置 LLM，我们要用到的工具，以及允许我们将 ReAct 框架与 LLM 和其他工具结合使用的代理。请注意，我们使用搜索 API 来搜索外部信息，并使用 LLM 作为数学工具。\n1 llm = OpenAI(model_name=\u0026#34;text-davinci-003\u0026#34; ,temperature=0)tools = load_tools([\u0026#34;google-serper\u0026#34;, \u0026#34;llm-math\u0026#34;], llm=llm)agent = initialize_agent(tools, llm, agent=\u0026#34;zero-shot-react-description\u0026#34;, verbose=True) 配置好之后，我们就可以用所需的查询或提示运行代理了。请注意，在这里，我们不会像论文中阐释的那样提供少样本的示例。\n1 agent.run(\u0026#34;奥利维亚·王尔德的男朋友是谁?他现在的年龄的0.23次方是多少?\u0026#34;) 链执行如下所示:\n1 \u0026gt; 正在输入新代理执行器链...... 我得查出奥利维亚·王尔德的男友是谁然后计算出他的年龄的 0.23 次方。操作: 搜索操作输入: “奥利维亚·王尔德的男友”观察: 奥利维亚·王尔德与杰森·苏代基斯在多年前订婚，在他们分手后，她开始与哈里·斯泰尔斯约会 — 参照他们的关系时间线。思考: 我需要找出哈里·斯泰尔斯的年龄。操作: 搜索操作输入: “哈里·斯泰尔斯的年龄”观察: 29 岁思考: 我需要计算 29 的 0.23 次方。操作: 计算器操作输入: 29^0.23观察: 答案: 2.169459462491557 思考: 现在我知道最终答案了。最终答案: 哈里·斯泰尔斯, 奥利维亚·王尔德的男朋友, 29 岁。他年龄的 0.23 次方是 2.169459462491557。 \u0026gt; 结束链。 我们得到如下输出:\n1 \u0026#34;哈里·斯泰尔斯, 奥利维亚·王尔德的男朋友, 29 岁。他年龄的 0.23 次方是 2.169459462491557。\u0026#34; Reflexion(自我反思) 通过语言反馈来强化基于语言的智能体的框架\n在高层次上，自我反思将来自环境的反馈（自由形式的语言或者标量）转换为语言反馈，也被称作 self-reflection，为下一轮中 LLM 智能体提供上下文。\n如上图所示，自我反思由三个不同的模型组成：\n参与者（Actor）：根据状态观测量生成文本和动作。参与者在环境中采取行动并接受观察结果，从而形成轨迹。链式思考（CoT） 和 ReAct 被用作参与者模型。此外，还添加了记忆组件为智能体提供额外的上下文信息。 评估者（Evaluator）：对参与者的输出进行评价。具体来说，它将生成的轨迹（也被称作短期记忆）作为输入并输出奖励分数。根据人物的不同，使用不同的奖励函数（决策任务使用LLM和基于规则的启发式奖励）。 自我反思（Self-Reflection）：生成语言强化线索来帮助参与者实现自我完善。这个角色由大语言模型承担，能够为未来的试验提供宝贵的反馈。自我反思模型利用奖励信号、当前轨迹和其持久记忆生成具体且相关的反馈，并存储在记忆组件中。智能体利用这些经验（存储在长期记忆中）来快速改进决策。 何时自我反思? 智能体需要从错误和尝试中学习 传统的强化学习方法失效 需要细致入微的反馈 可解释性和直接记忆很重要 自我反思在以下任务中是有效的：\n序列决策：自我反思提高了智能体在 AlfWorld 任务中的表现，涉及在各种环境中导航并完成多步目标。 推理：自我反思提高了 HotPotQA 上智能体的性能，HotPotQA 是一个需要对多个文档进行推理的问答数据集。 编程：自我反思的智能体在 HumanEval 和 MBPP 等基准测试上编写出了更好的代码，在某些情况下实现 SOTA 结果。 以下是自我反思的一些限制：\n依赖自我评估能力：反思依赖于智能体准确评估其表现并产生有用反思的能力。这可能是具有挑战性的，尤其是对于复杂的任务，但随着模型功能的不断改进，预计自我反思会随着时间的推移而变得更好。 长期记忆限制：自我反思使用最大容量的滑动窗口，但对于更复杂的任务，使用向量嵌入或 SQL 数据库等高级结构可能会更有利。 代码生成限制：测试驱动开发在指定准确的输入输出映射方面存在限制（例如，受硬件影响的非确定性生成器函数和函数输出）。 多维态思维链提示方法 基于图的提示 meta-prompting ","permalink":"https://tuanzi-wow.cn/promptengineering/prompt-techniques/","summary":"\u003ch1 id=\"提示技术\"\u003e提示技术\u003c/h1\u003e\n\u003ch2 id=\"零样本提示\"\u003e零样本提示\u003c/h2\u003e\n\u003cp\u003e没有向模型提供示例\u003c/p\u003e\n\u003ch2 id=\"少样本提示few-shot\"\u003e少样本提示(few-shot)\u003c/h2\u003e\n\u003cp\u003e提供少量示例以激发上下文学习\u003c/p\u003e\n\u003ch3 id=\"限制处理复杂任务\"\u003e限制:处理复杂任务\u003c/h3\u003e\n\u003ch2 id=\"链式思考提示chain-of-thought-prompting\"\u003e链式思考提示(Chain-of-Thought Prompting)\u003c/h2\u003e\n\u003cp\u003eCoT提示通过中间推理步骤实现了复杂的推理能力\n可以与few-shot结合\u003c/p\u003e\n\u003ch3 id=\"零样本cot提示\"\u003e零样本COT提示\u003c/h3\u003e\n\u003cp\u003e基本上涉及\u0026quot;将让我们逐步思考\u0026quot;添加到原始提示中\u003c/p\u003e","title":"Prompt Engineering · 提示技术"},{"content":"Tools 定义: Tool是agents调用来执行操作的组件,通过定义良好的输入和输出来让模型和外部世界交互\nTools封装了一个可调用的函数及其输入模式,允许模型决定什么时候调用Tools以及传入的参数\nTool主要用语言:\n信息检索:与外部数据源进行交互检索信息,如数据库和Web搜索引擎.主要目标是增强model的知识,使其能够回答原本无法回答的问题 执行操作:目标是自动化原本需要人工干预或者显式编程的任务 ps:Toolc calling称为model能力但是实际上由客户端提供ToolCalling逻辑\nQuick Start 两个示例:一个信息检索,一个执行操作\n信息检索 在DataTimeTool类中实现一个Tool来获取日期和时间(此处不关心具体实现方式)\n1 2 3 4 5 6 7 8 9 10 11 12 import java.time.LocalDateTime; import org.springframework.ai.tool.annotation.Tool; import org.springframework.context.i18n.LocaleContextHolder; class DateTimeTools { @Tool(description = \u0026#34;Get the current date and time in the user\u0026#39;s timezone\u0026#34;) String getCurrentDateTime() { return LocalDateTime.now().atZone(LocaleContextHolder.getTimeZone().toZoneId()).toString(); } } 如何让LLM了解到这个Tool的存在:通过调用Chatclient的tool()方法通知LLM 注意 tool(datetimetool)中的datetimetool需要实例化(也就你JAVA这么多事)\n1 2 3 4 5 6 7 8 9 10 ChatModel chatModel = ...; String response = ChatClient.create(chatModel) .prompt(\u0026#34;What day is tomorrow?\u0026#34;) .tools(new DateTimeTools()) .call() .content(); System.out.println(response); // 输出：Tomorrow is 2015-10-21. 执行操作 和上面差不多自己看了\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import org.springframework.ai.tool.annotation.Tool; import org.springframework.context.i18n.LocaleContextHolder; class DateTimeTools { @Tool(description = \u0026#34;Get the current date and time in the user\u0026#39;s timezone\u0026#34;) String getCurrentDateTime() { return LocalDateTime.now().atZone(LocaleContextHolder.getTimeZone().toZoneId()).toString(); } @Tool(description = \u0026#34;Set a user alarm for the given time, provided in ISO-8601 format\u0026#34;) void setAlarm(String time) { LocalDateTime alarmTime = LocalDateTime.parse(time, DateTimeFormatter.ISO_DATE_TIME); System.out.println(\u0026#34;Alarm set for \u0026#34; + alarmTime); } } 那么有的同学就要问了: 这里只调用了datetimetool类而不是具体的工具,他是怎么知道选择哪个方法的 这就是你SpringAI叔叔的强大之处了,@Tool:会将该方法自动注册为一个tool 所以你们臭学JAVA的还不好好学Spring,Spring Boot,Spring MVC?\n概述 1.Tool定义:名称,描述和传入参数 2.调用:Model发送带有Tool名称和参数的相应 3.通过名称选择tool并传入参数执行 4.toolcall结果由程序处理 5.结果传回Model 6.Model根据结果作为附加上下文生成最终响应\nTools 是 tool calling 的构建块，它们由 ToolCallback 接口建模。Spring AI 提供了从方法和函数指定 ToolCallback(s) 的内置支持，但您始终可以定义自己的 ToolCallback 实现以支持更多用例。\nChatModel 实现透明地将 tool call 请求分派到相应的 ToolCallback 实现，并将 tool call 结果发送回 model，最终生成最终响应。它们使用 ToolCallingManager 接口来执行此操作，该接口负责管理 tool 执行生命周期。\nChatClient 和 ChatModel 都接受 ToolCallback 对象列表，以使 tools 可用于 model 和最终执行它们的 ToolCallingManager。\n除了直接传递 ToolCallback 对象外，您还可以传递 tool 名称列表，这些名称将使用 ToolCallbackResolver 接口动态解析。\n创建工具 Spring AI提供了两种从方法指定Tools(即ToolCallback(s))的内置支持\n声明式:使用@Tool注解 编程式:使用低级MethodToolCallback实现 方法作为Tool 声明式规范 @Tool 1 2 3 4 5 6 7 8 class DateTimeTools { @Tool(description = \u0026#34;Get the current date and time in the user\u0026#39;s timezone\u0026#34;) String getCurrentDateTime() { return LocalDateTime.now().atZone(LocaleContextHolder.getTimeZone().toZoneId()).toString(); } } @Tool注解允许你提供的信息:\nname:如果未提供,将使用方法名称 description:tool描述;若未提供,使用方法名称代替.但是强烈建议详细描述,对于model理解tool的用途以及when to use and how to use很重要 returnDirect:结果是否直接传回model;为布尔值 resultConverter:将toolcall的结果转化为String Object以 ToolCallResultConverter 实现 NOTE: Spring AI 为 @Tool 注解方法的 AOT 编译提供内置支持，只要包含方法的类是 Spring bean（例如 @Component）。否则，您需要向 GraalVM 编译器提供必要的配置。例如，通过用 @RegisterReflection(memberCategories = MemberCategory.INVOKE_DECLARED_METHODS) 注解类。\n如果方法返回值，返回类型必须是可序列化类型，因为结果将被序列化并发送回 model。\n@ToolParam 注解可用于提供有关输入参数的附加信息，例如描述或参数是必需还是可选的。默认情况下，所有输入参数都被视为必需\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import org.springframework.ai.tool.annotation.Tool; import org.springframework.ai.tool.annotation.ToolParam; class DateTimeTools { @Tool(description = \u0026#34;Set a user alarm for the given time\u0026#34;) void setAlarm(@ToolParam(description = \u0026#34;Time in ISO-8601 format\u0026#34;) String time) { LocalDateTime alarmTime = LocalDateTime.parse(time, DateTimeFormatter.ISO_DATE_TIME); System.out.println(\u0026#34;Alarm set for \u0026#34; + alarmTime); } } @ToolParam允许你提供的有关tool参数的关键信息:\ndescription：参数的描述，model 可以使用它来更好地理解如何使用它。例如，参数应该是什么格式，允许什么值等。 required：参数是必需还是可选的。默认情况下，所有参数都被视为必需。 ","permalink":"https://tuanzi-wow.cn/agent/spring-ai-tools/","summary":"\u003ch1 id=\"tools\"\u003eTools\u003c/h1\u003e\n\u003ch2 id=\"定义\"\u003e定义:\u003c/h2\u003e\n\u003cp\u003eTool是agents调用来执行操作的组件,通过\u003cstrong\u003e定义良好的输入和输出\u003c/strong\u003e来让模型和外部世界交互\u003c/p\u003e\n\u003cp\u003eTools封装了一个可调用的函数及其输入模式,允许模型决定什么时候调用Tools以及传入的参数\u003c/p\u003e","title":"Spring AI Tools"},{"content":"Transformer框架 2.1 注意力机制 2.1.1 什么是注意力机制 从计算机视觉为起源发展起来的神经网络,其核心架构有三种\n前馈神经网络（Feedforward Neural Network，FNN），即数据从输入层单向流动到输出层，无循环结构，各层之间通过全连接或特定方式传递信息，其中多层感知机（Multi-Layer Perceptron，MLP）是最常见的形式，每一层的神经元都和上下两层的每一个神经元完全连接，如图2.1所示: 图2.1 全连接神经网络\n卷积神经网络（Convolutional Neural Network，CNN），即训练参数量远小于全连接神经网络的卷积层来进行特征提取和学习，如图2.2所示: 图2.2 卷积神经网络\n循环神经网络（Recurrent Neural Network，RNN），能够使用历史信息作为输入、包含环和自重复的网络，如图2.3所示: 由于 NLP 任务所需要处理的文本往往是序列，因此专用于处理序列、时序数据的 RNN 往往能够在 NLP 任务上取得最优的效果\n虽然RNN及LSTM具有捕捉时序信息,适合序列生成的优点,却有两个难以弥补的缺陷: 1.序列依序计算的模式能够很好地模拟时序信息,但是限制了计算机并行计算的能力 2.RNN难以捕捉长序列的相关关系\n什么是注意力机制呢?\n核心思想:将重点注意力集中在一个或者几个Token,从而取得更高效高质的计算效果 三个核心变量:Query(查寻值),Key(键值),Value(真值):\n一个例子方便理解,可看可不看:\n当我们有一篇新闻报道，我们想要找到这个报道的时间，那么，我们的 Query 可以是类似于“时间”、“日期”一类的向量（为了便于理解，此处使用文本来表示，但其实际是稠密的向量），Key 和 Value 会是整个文本。通过对 Query 和 Key 进行运算我们可以得到一个权重，这个权重其实反映了从 Query 出发，对文本每一个 token 应该分布的注意力相对大小。通过把权重和 Value 进行运算，得到的最后结果就是从 Query 出发计算整个文本注意力得到的结果。\n注意力机制的特点是通过计算 Query 与Key的相关性为真值加权求和，从而拟合序列中每个词同其他词的相关关系。\n2.1.2 深入理解注意力机制 给不同 Key 所赋予的不同权重，就是我们所说的注意力分数，也就是为了查询到 Query，我们应该赋予给每一个 Key 多少注意力 但是，如何针对每一个 Query，计算出对应的注意力分数呢？从直观上讲，我们可以认为 Key 与 Query 相关性越高，则其所应该赋予的注意力权重就越大。但是，我们如何能够找到一个合理的、能够计算出正确的注意力分数的方法呢 $$ v \\cdot w = \\sum_i v_i w_i $$ 根据词向量的定义，语义相似的两个词对应的词向量的点积应该大于 0，而语义不相似的词向量点积应该小于 0。\n那么，我们就可以用点积来计算词之间的相似度。假设我们的 Query 为“fruit”，对应的词向量为 q；我们的 Key 对应的词向量为： $$ K=\\begin{bmatrix} v_{\\text{apple}} \\ v_{\\text{banana}} \\ v_{\\text{chair}} \\end{bmatrix} $$ 则我们可以计算 Query 和每一个 Key 的相似程度： $$ x = qK^T $$ 此处的 K 即为将所有 Key 对应的词向量堆叠形成的矩阵。基于矩阵乘法的定义，x 即为 q 与每一个 Key 向量的点积。\n现在我们得到的 x 即反映了 Query 和每一个 Key 的相似程度，我们再通过一个 Softmax 层将其转化为和为 1 的权重： $$ \\operatorname{softmax}(x)_i = \\frac{e^{x_i}}{\\sum_j e^{x_j}} $$ 这样，得到的向量就能够反映 Query 和每一个 Key 的相似程度，同时所有权重相加为 1，也就是我们的注意力分数。\n最后，我们再将得到的注意力分数和值向量做对应乘积即可。根据上述过程，我们就可以得到注意力机制计算的基本公式： $$ \\operatorname{Attention}(Q,K,V) = \\operatorname{softmax}(qK^T)V $$ 不过，此时的值还是一个标量，同时，我们此次只查询了一个 Query。我们可以将值转化为维度为 d_v 的向量，同时一次性查询多个 Query，同样将多个 Query 对应的词向量堆叠在一起形成矩阵 Q，得到公式： $$ \\operatorname{Attention}(Q,K,V) = \\operatorname{softmax}(QK^T)V $$ 目前，我们离标准的注意力机制公式还差最后一步。在上一个公式中，如果 Q 和 K 对应的维度 d_k 比较大，QK^T 的结果数值可能会变得较大，使 Softmax 输出过于集中，从而影响梯度的稳定性。因此，我们要将 Q 和 K 乘积的结果做一个缩放： $$ \\operatorname{Attention}(Q,K,V) = \\operatorname{softmax} \\left( \\frac{QK^T}{\\sqrt{d_k}} \\right)V $$\n2.1.3 注意力机制的实现 使用Pytorch\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 \u0026#39;\u0026#39;\u0026#39;注意力计算函数\u0026#39;\u0026#39;\u0026#39; def attention(query, key, value, dropout=None): \u0026#39;\u0026#39;\u0026#39; args: query: 查询值矩阵 key: 键值矩阵 value: 真值矩阵 \u0026#39;\u0026#39;\u0026#39; # 获取键向量的维度，键向量的维度和值向量的维度相同 d_k = query.size(-1) # 计算Q与K的内积并除以根号dk # transpose——相当于转置 scores = torch.matmul(query, key.transpose(-2, -1)) / math.sqrt(d_k) # Softmax p_attn = scores.softmax(dim=-1) if dropout is not None: p_attn = dropout(p_attn) # 采样 # 根据计算结果对value进行加权求和 return torch.matmul(p_attn, value), p_attn 2.1.4 自注意力 注意力机制的本质是对两段序列的元素依次进行相似度计算，寻找出一个序列的每个元素对另一个序列的每个元素的相关度，然后基于相关度进行加权，即分配注意力。而这两段序列即是我们计算过程中 Q、K、V 的来源\n但是，在我们的实际应用中，我们往往只需要计算 Query 和 Key 之间的注意力结果，很少存在额外的真值 Value。也就是说，我们其实只需要拟合两个文本序列。在经典的 注意力机制中，Q 往往来自于一个序列，K 与 V 来自于另一个序列，都通过参数矩阵计算得到，从而可以拟合这两个序列之间的关系。例如在 Transformer 的 Decoder 结构中，Q 来自于 Decoder 的输入，K 与 V 来自于 Encoder 的输出，从而拟合了编码信息与历史信息之间的关系，便于综合这两种信息实现未来的预测。\n但在 Transformer 的 Encoder 结构中，使用的是 注意力机制的变种 —— 自注意力（self-attention，自注意力）机制。所谓自注意力，即是计算本身序列中每个元素对其他元素的注意力分布，即在计算过程中，Q、K、V 都由同一个输入通过不同的参数矩阵计算得到。在 Encoder 中，Q、K、V 分别是输入对参数矩阵 Wq、Wk、WvW**q、W**k、W**v 做积得到，从而拟合输入语句中每一个 token 对其他所有 token 的关系。\n通过自注意力机制，我们可以找到一段文本中每一个 token 与其他所有 token 的相关关系大小，从而建模文本之间的依赖关系。在代码中的实现，self-attention 机制其实是通过给 Q、K、V 的输入传入同一个参数实现的：\n1 2 # attention 为上文定义的注意力计算函数 attention(x, x, x) 2.1.5 掩码自注意力 掩码自注意力，即 Mask Self-Attention，是指使用注意力掩码的自注意力机制。掩码的作用是遮蔽一些特定位置的 token，模型在学习的过程中，会忽略掉被遮蔽的 token。 使用注意力掩码的核心动机是让模型只能使用历史信息进行预测而不能看到未来信息\n使用注意力机制的 Transformer 模型也是通过类似于 n-gram 的语言模型任务来学习的 例子:如，如果待学习的文本序列是 【BOS】I like you【EOS】，那么，模型会按如下顺序进行预测和学习：\n1 2 3 4 Step 1：输入 【BOS】，输出 I Step 2：输入 【BOS】I，输出 like Step 3：输入 【BOS】I like，输出 you Step 4：输入 【BOS】I like you，输出 【EOS】 但是上述过程是一个串行的过程,计算效率很低 针对这个问题，Transformer 就提出了掩码自注意力的方法掩码自注意力会生成一串掩码，来遮蔽未来信息。例如，我们待学习的文本序列仍然是 【BOS】I like you【EOS】，我们使用的注意力掩码是【MASK】，那么模型的输入为：\n1 2 3 4 5 \u0026lt;BOS\u0026gt; 【MASK】【MASK】【MASK】【MASK】 \u0026lt;BOS\u0026gt; I 【MASK】 【MASK】【MASK】 \u0026lt;BOS\u0026gt; I like 【MASK】【MASK】 \u0026lt;BOS\u0026gt; I like you 【MASK】 \u0026lt;BOS\u0026gt; I like you \u0026lt;/EOS\u0026gt; 上述输入不再是串行的过程，而可以一起并行地输入到模型中\n观察掩码发现:是一个和文本序列等长的上三角矩阵 我们可以简单地通过创建一个和输入同等长度的上三角矩阵作为注意力掩码，再使用掩码来遮蔽掉输入即可为注意力掩码，再使用掩码来遮蔽掉输入即可。也就是说，当输入维度为 （batch_size, seq_len, hidden_size）时，我们的 Mask 矩阵维度一般为 (1, seq_len, seq_len)（通过广播实现同一个 batch 中不同样本的计算）。\n在具体实现中，我们通过以下代码生成 Mask 矩阵：\n1 2 3 4 5 # 创建一个上三角矩阵，用于遮蔽未来信息。 # 先通过 full 函数创建一个 1 * seq_len * seq_len 的矩阵 mask = torch.full((1, args.max_seq_len, args.max_seq_len), float(\u0026#34;-inf\u0026#34;)) # triu 函数的功能是创建一个上三角矩阵 mask = torch.triu(mask, diagonal=1) 生成的 Mask 矩阵会是一个上三角矩阵，上三角位置的元素均为 -inf，其他位置的元素置为0。\n在注意力计算时，我们会将计算得到的注意力分数与这个掩码做和，再进行 Softmax 操作：\n1 2 3 # 此处的 scores 为计算得到的注意力分数，mask 为上文生成的掩码矩阵 scores = scores + mask[:, :seqlen, :seqlen] scores = F.softmax(scores.float(), dim=-1).type_as(xq)Copy to clipboardErrorCopied 通过做求和，上三角区域（也就是应该被遮蔽的 token 对应的位置）的注意力分数结果都变成了 -inf，而下三角区域的分数不变。再做 Softmax 操作，-inf 的值在经过 Softmax 之后会被置为 0，从而忽略了上三角区域计算的注意力分数，从而实现了注意力遮蔽。\n2.1.6 多头注意力 注意力机制可以实现并行化与长期依赖关系拟合，但一次注意力计算只能拟合一种相关关系，单一的注意力机制很难全面拟合语句序列里的相关关系\n因此 Transformer 使用了多头注意力机制（Multi-Head Attention），即同时对一个语料进行多次注意力计算，每次注意力计算都能拟合不同的关系，将最后的多次结果拼接起来作为最后的输出，即可更全面深入地拟合语言信息。\n事实上，所谓的多头注意力机制其实就是将原始的输入序列进行多组的自注意力处理；然后再将每一组得到的自注意力结果拼接起来，再通过一个线性层进行处理，得到最终的输出。我们用公式可以表示为：\n1 2 3 \\operatorname{MultiHead}(Q,K,V) = \\operatorname{Concat}(\\operatorname{head}_1,\\ldots,\\operatorname{head}_h)W^O 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 import torch.nn as nn import torch \u0026#39;\u0026#39;\u0026#39;多头自注意力计算模块\u0026#39;\u0026#39;\u0026#39; class MultiHeadAttention(nn.Module): def __init__(self, args: ModelArgs, is_causal=False): # 构造函数 # args: 配置对象 super().__init__() # 隐藏层维度必须是头数的整数倍，因为后面我们会将输入拆成头数个矩阵 assert args.dim % args.n_heads == 0 # 每个头的维度，等于模型维度除以头的总数。 self.head_dim = args.dim // args.n_heads self.n_heads = args.n_heads # Wq, Wk, Wv 参数矩阵，每个参数矩阵为 n_embd x dim # 这里通过三个组合矩阵来代替了n个参数矩阵的组合，其逻辑在于矩阵内积再拼接其实等同于拼接矩阵再内积， # 不理解的读者可以自行模拟一下，每一个线性层其实相当于n个参数矩阵的拼接 self.wq = nn.Linear(args.n_embd, self.n_heads * self.head_dim, bias=False) self.wk = nn.Linear(args.n_embd, self.n_heads * self.head_dim, bias=False) self.wv = nn.Linear(args.n_embd, self.n_heads * self.head_dim, bias=False) # 输出权重矩阵，维度为 dim x dim（head_dim = dim / n_heads） self.wo = nn.Linear(self.n_heads * self.head_dim, args.dim, bias=False) # 注意力的 dropout self.attn_dropout = nn.Dropout(args.dropout) # 残差连接的 dropout self.resid_dropout = nn.Dropout(args.dropout) self.is_causal = is_causal # 创建一个上三角矩阵，用于遮蔽未来信息 # 注意，因为是多头注意力，Mask 矩阵比之前我们定义的多一个维度 if is_causal: mask = torch.full((1, 1, args.max_seq_len, args.max_seq_len), float(\u0026#34;-inf\u0026#34;)) mask = torch.triu(mask, diagonal=1) # 注册为模型的缓冲区 self.register_buffer(\u0026#34;mask\u0026#34;, mask) def forward(self, q: torch.Tensor, k: torch.Tensor, v: torch.Tensor): # 获取批次大小和序列长度，[batch_size, seq_len, dim] bsz, seqlen, _ = q.shape # 计算查询（Q）、键（K）、值（V）,输入通过参数矩阵层，维度为 (B, T, n_embed) x (n_embed, dim) -\u0026gt; (B, T, dim) xq, xk, xv = self.wq(q), self.wk(k), self.wv(v) # 将 Q、K、V 拆分成多头，维度为 (B, T, n_head, dim // n_head)，然后交换维度，变成 (B, n_head, T, dim // n_head) # 因为在注意力计算中我们是取了后两个维度参与计算 # 为什么要先按B*T*n_head*C//n_head展开再互换1、2维度而不是直接按注意力输入展开，是因为view的展开方式是直接把输入全部排开， # 然后按要求构造，可以发现只有上述操作能够实现我们将每个头对应部分取出来的目标 xq = xq.view(bsz, seqlen, self.n_heads, self.head_dim) xk = xk.view(bsz, seqlen, self.n_heads, self.head_dim) xv = xv.view(bsz, seqlen, self.n_heads, self.head_dim) xq = xq.transpose(1, 2) xk = xk.transpose(1, 2) xv = xv.transpose(1, 2) # 注意力计算 # 计算 QK^T / sqrt(d_k)，维度为 (B, nh, T, hs) x (B, nh, hs, T) -\u0026gt; (B, nh, T, T) scores = torch.matmul(xq, xk.transpose(2, 3)) / math.sqrt(self.head_dim) # 掩码自注意力必须有注意力掩码 if self.is_causal: assert hasattr(self, \u0026#39;mask\u0026#39;) # 这里截取到序列长度，因为有些序列可能比 max_seq_len 短 scores = scores + self.mask[:, :, :seqlen, :seqlen] # 计算 softmax，维度为 (B, nh, T, T) scores = F.softmax(scores.float(), dim=-1).type_as(xq) # 做 Dropout scores = self.attn_dropout(scores) # V * Score，维度为(B, nh, T, T) x (B, nh, T, hs) -\u0026gt; (B, nh, T, hs) output = torch.matmul(scores, xv) # 恢复时间维度并合并头。 # 将多头的结果拼接起来, 先交换维度为 (B, T, n_head, dim // n_head)，再拼接成 (B, T, n_head * dim // n_head) # contiguous 函数用于重新开辟一块新内存存储，因为Pytorch设置先transpose再view会报错， # 因为view直接基于底层存储得到，然而transpose并不会改变底层存储，因此需要额外存储 output = output.transpose(1, 2).contiguous().view(bsz, seqlen, -1) # 最终投影回残差流。 output = self.wo(output) output = self.resid_dropout(output) return output 2.2 Encoder-Decoder Transformer框架的两个核心组件: Encoder(编码器)和Decoder(解码器)\n2.2.1 Seq2Seq模型 Seq2Seq:序列到序列 具体而言, 是指模型输入的是一个自然语言序列 input=(x1,x2,x3\u0026hellip;xn)inpu**t=(x1,x2,x3\u0026hellip;x**n) ，输出的是一个可能不等长的自然语言序列 output=(y1,y2,y3\u0026hellip;ym)output=(y1,y2,y3\u0026hellip;y**m)\n流程解析: 对于 Seq2Seq 任务，一般的思路是对自然语言序列进行编码再解码。所谓编码，就是将输入的自然语言序列通过隐藏层编码成能够表征语义的向量（或矩阵），可以简单理解为更复杂的词向量表示。而解码，就是对输入的自然语言序列编码得到的向量或矩阵通过隐藏层输出，再解码成对应的自然语言目标序列。通过编码再解码，就可以实现 Seq2Seq 任务。 而其中的编码和解码工作,就是有Encoder和Decoder完成的\nTransformer的架构:\nTransformer 由 Encoder 和 Decoder 组成，每一个 Encoder（Decoder）又由 6个 Encoder（Decoder)Layer 组成。输入源序列会进入 Encoder 进行编码，到 Encoder Layer 的最顶层再将编码结果输出给 Decoder Layer 的每一层，通过 Decoder 解码后就可以得到输出目标序列了 Encoder 和 Decoder 内部传统神经网络的经典结构——前馈神经网络（FNN）、层归一化（Layer Norm）和残差连接（Residual Connection）\n2.2.2 前馈神经网络(FNN) 每一层的神经元都和上下两层的每一个神经元完全连接的网络结构 每一个 Encoder Layer 都包含一个上文讲的注意力机制和一个前馈神经网络\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class MLP(nn.Module): \u0026#39;\u0026#39;\u0026#39;前馈神经网络\u0026#39;\u0026#39;\u0026#39; def __init__(self, dim: int, hidden_dim: int, dropout: float): super().__init__() # 定义第一层线性变换，从输入维度到隐藏维度 self.w1 = nn.Linear(dim, hidden_dim, bias=False) # 定义第二层线性变换，从隐藏维度到输入维度 self.w2 = nn.Linear(hidden_dim, dim, bias=False) # 定义dropout层，用于防止过拟合 self.dropout = nn.Dropout(dropout) def forward(self, x): # 前向传播函数 # 首先，输入x通过第一层线性变换和RELU激活函数 # 最后，通过第二层线性变换和dropout层 return self.dropout(self.w2(F.relu(self.w1(x)))) Transformer的FNN的架构: 由两个线性层中间加一个 RELU 激活函数组成的，以及前馈神经网络还加入了一个 Dropout 层来防止过拟合 Dropout 层只在训练时开启，推理/测试阶段关闭\n2.2.3 层归一化(Layer Norm) 神经网络的主流归一化操作有: 批归一化(Batch Norm)和层归一化(Layer Norm) 归一化核心是为了让不同层输入的取值范围或者分布能够比较一致 误差的产生: 本层的输入为上一层的输出,随着参数的更新导致的输入(输出)分布发生改变和深度的加深,差异会越来越大,然而预测的条件分布始终是相同的,就造成了误差 因此需要归一化:归一化为标准正态分布\n批归一化是指在一个 mini-batch 上进行归一化，相当于对一个 batch 对样本拆分出来一部分，首先计算样本的均值： $$ μj=\\frac{1}{m}\\sum_{i=1}^{m}Z_{ji} $$ 其中，Zji 是样本 i 在第 j 个维度上的值，m 就是 mini-batch 的大小。\n再计算样本的方差： $$ σ2=\\frac{1}{m}\\sum_{i=1}^{m}(Z_{ji}-\\mu_j)^2 $$ 最后，对每个样本的值减去均值再除以标准差来将这一个 mini-batch 的样本的分布转化为标准正态分布： $$ Z~j=\\frac{Z_j-\\mu_j}{\\sqrt{\\sigma^2+\\epsilon}} $$ 此处加上 ϵ 这一极小量是为了避免分母为0。\n但是批归一化存在缺陷.例如: 1.显存有限,mini-batch比较小,批归一化无法返回全局信息,效果比较差 2.对于在时间维度展开的 RNN，不同句子的同一分布大概率不同，所以 Batch Norm 的归一化会失去意义 3.在训练时，Batch Norm 需要保存每个 step 的统计信息（均值和方差）。在测试时，由于变长句子的特性，测试集可能出现比训练集更长的句子，所以对于后面位 置的 step，是没有训练的统计量使用的 4.应用 Batch Norm，每个 step 都需要去保存和计算 batch 统计量，耗时又耗力\n因此出现了层归一化: Layer Norm 的归一化方式其实和 Batch Norm 是完全一样的，只是统计统计量的维度不同\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class LayerNorm(nn.Module): \u0026#39;\u0026#39;\u0026#39; Layer Norm 层\u0026#39;\u0026#39;\u0026#39; def __init__(self, features, eps=1e-6): super().__init__() # 线性矩阵做映射 self.a_2 = nn.Parameter(torch.ones(features)) self.b_2 = nn.Parameter(torch.zeros(features)) self.eps = eps def forward(self, x): # 在统计每个样本所有维度的值，求均值和方差 mean = x.mean(-1, keepdim=True) # mean: [bsz, max_len, 1] std = x.std(-1, keepdim=True) # std: [bsz, max_len, 1] # 注意这里也在最后一个维度发生了广播 return self.a_2 * (x - mean) / (std + self.eps) + self.b_2 2.2.4 残差连接 为了避免模型退化,使用残差连接的思想来连接每一个子层: 即下一层的输入不仅是上一层的输出，还包括上一层的输入.允许最底层信息直接传到最高层,让高层专注残差的学习 例如: 在 Encoder 中，在第一个子层，输入会先进行层归一化（Layer Norm），然后进入多头自注意力层，其输出会与原输入相加。在第二个子层也是一样。\n1 2 3 x=x+MultiHeadSelfAttention(LayerNorm(x)) output=x+FNN(LayerNorm(x)) 在代码实现中:\n1 2 3 4 # 注意力计算 h = x + self.attention.forward(self.attention_norm(x)) # 经过前馈神经网络 out = h + self.feed_forward.forward(self.fnn_norm(h)) 在上文代码中，self.attention_norm 和 self.fnn_norm 都是 LayerNorm 层，self.attn 是注意力层，而 self.feed_forward 是前馈神经网络。\n2.2.5 Encoder Encoder 由 N 个 Encoder Layer 组成，每一个 Encoder Layer 包括一个注意力层和一个前馈神经网络\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class EncoderLayer(nn.Module): \u0026#39;\u0026#39;\u0026#39;Encoder层\u0026#39;\u0026#39;\u0026#39; def __init__(self, args): super().__init__() # 一个 Layer 中有两个 LayerNorm，分别在 Attention 之前和 MLP 之前 self.attention_norm = LayerNorm(args.n_embd) # Encoder 不需要掩码，传入 is_causal=False self.attention = MultiHeadAttention(args, is_causal=False) self.fnn_norm = LayerNorm(args.n_embd) self.feed_forward = MLP(args.dim, args.dim, args.dropout) def forward(self, x): # Layer Norm norm_x = self.attention_norm(x) # 自注意力 h = x + self.attention.forward(norm_x, norm_x, norm_x) # 经过前馈神经网络 out = h + self.feed_forward.forward(self.fnn_norm(h)) return out 然后我们搭建一个 Encoder，由 N 个 Encoder Layer 组成，在最后会加入一个 Layer Norm 实现规范化:\n1 2 3 4 5 6 7 8 9 10 11 12 13 class Encoder(nn.Module): \u0026#39;\u0026#39;\u0026#39;Encoder 块\u0026#39;\u0026#39;\u0026#39; def __init__(self, args): super(Encoder, self).__init__() # 一个 Encoder 由 N 个 Encoder Layer 组成 self.layers = nn.ModuleList([EncoderLayer(args) for _ in range(args.n_layer)]) self.norm = LayerNorm(args.n_embd) def forward(self, x): \u0026#34;分别通过 N 层 Encoder Layer\u0026#34; for layer in self.layers: x = layer(x) return self.norm(x) 2.2.6 Decoder 我们也可以先搭建 Decoder Layer，再将 N 个 Decoder Layer 组装为 Decoder\nDecoder 由两个注意力层和一个前馈神经网络组成。第一个注意力层是一个掩码自注意力层，即使用 Mask 的注意力计算，保证每一个 token 只能使用该 token 之前的注意力分数；第二个注意力层是一个多头注意力层，该层将使用第一个注意力层的输出作为 query，使用 Encoder 的输出作为 key 和 value，来计算注意力分数。最后，再经过前馈神经网络：\n1 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 class DecoderLayer(nn.Module): \u0026#39;\u0026#39;\u0026#39;解码层\u0026#39;\u0026#39;\u0026#39; def __init__(self, args): super().__init__() # 一个 Layer 中有三个 LayerNorm，分别在 Mask Attention 之前、Self Attention 之前和 MLP 之前 self.attention_norm_1 = LayerNorm(args.n_embd) # Decoder 的第一个部分是 Mask Attention，传入 is_causal=True self.mask_attention = MultiHeadAttention(args, is_causal=True) self.attention_norm_2 = LayerNorm(args.n_embd) # Decoder 的第二个部分是 类似于 Encoder 的 Attention，传入 is_causal=False self.attention = MultiHeadAttention(args, is_causal=False) self.ffn_norm = LayerNorm(args.n_embd) # 第三个部分是 MLP self.feed_forward = MLP(args.dim, args.dim, args.dropout) def forward(self, x, enc_out): # Layer Norm norm_x = self.attention_norm_1(x) # 掩码自注意力 x = x + self.mask_attention.forward(norm_x, norm_x, norm_x) # 多头注意力 norm_x = self.attention_norm_2(x) h = x + self.attention.forward(norm_x, enc_out, enc_out) # 经过前馈神经网络 out = h + self.feed_forward.forward(self.ffn_norm(h)) return out 然后同样的，我们搭建一个 Decoder 块：\n1 2 3 4 5 6 7 8 9 10 11 12 13 class Decoder(nn.Module): \u0026#39;\u0026#39;\u0026#39;解码器\u0026#39;\u0026#39;\u0026#39; def __init__(self, args): super(Decoder, self).__init__() # 一个 Decoder 由 N 个 Decoder Layer 组成 self.layers = nn.ModuleList([DecoderLayer(args) for _ in range(args.n_layer)]) self.norm = LayerNorm(args.n_embd) def forward(self, x, enc_out): \u0026#34;Pass the input (and mask) through each layer in turn.\u0026#34; for layer in self.layers: x = layer(x, enc_out) return self.norm(x) 2.3 搭建一个Transformer 2.3.1 Embedding层 将自然语言转化为机器能够处理的向量 Embedding:存储固定大小的词典的嵌入向量查找表 在进入神经网络之前.自然语言输入通过tokenizer,将自然语言输入切分成token并且转化成一个固定的index Embedding的输入:一个形状为 （batch_size，seq_len，1）: 第一个维度是一次批处理的数量，第二个维度是自然语言序列的长度，第三个维度则是 token 经过 tokenizer 转化成的 index 值\n而 Embedding 内部其实是一个可训练的（Vocab_size，embedding_dim）的权重矩阵，词表里的每一个值，都对应一行维度为 embedding_dim 的向量。对于输入的值，会对应到这个词向量，然后拼接成（batch_size，seq_len，embedding_dim）的矩阵输出。\n1 self.tok_embeddings = nn.Embedding(args.vocab_size, args.dim) 2.2.3 位置编码 注意计算方式的缺陷: 导致序列中相对位置的丢失 例如:对于序列中的每一个 token，其他各个位置对其来说都是平等的，即“我喜欢你”和“你喜欢我”在注意力机制看来是完全相同的 解决方法:位置编码位置编码，即根据序列中 token 的相对位置对其进行编码，再将位置编码加入词向量编码中。位置编码的方式有很多，Transformer 使用了正余弦函数来进行位置编码（绝对位置编码Sinusoidal），其编码方式为： $$ PE(pos,2i)=sin(pos/10000 2i/d model ​\n) $$\n$$ PE(pos,2i+1)=cos(pos/10000 2i/d model ​\n) $$\n上式中，pos 为 token 在句子中的位置，2i 和 2i+1 则指示了位置编码向量的维度索引是奇数还是偶数，从上式中我们可以看出对于奇数维度和偶数维度，Transformer 采用了不同的函数进行编码。 例子:\n则经过位置编码后的词向量为：\nxPE=[0.10.20.30.40.20.30.40.50.30.40.50.60.40.50.60.7]+[sin⁡(0100000)cos⁡(0100000)sin⁡(0100002/4)cos⁡(0100002/4)sin⁡(1100000)cos⁡(1100000)sin⁡(1100002/4)cos⁡(1100002/4)sin⁡(2100000)cos⁡(2100000)sin⁡(2100002/4)cos⁡(2100002/4)sin⁡(3100000)cos⁡(3100000)sin⁡(3100002/4)cos⁡(3100002/4)]=[0.11.20.31.41.0410.840.411.491.209−0.0160.521.590.541−0.4890.8951.655]xPE=0.10.20.30.40.20.30.40.50.30.40.50.60.40.50.60.7+sin(1000000)sin(1000001)sin(1000002)sin(1000003)cos(1000000)cos(1000001)cos(1000002)cos(1000003)sin(100002/40)sin(100002/41)sin(100002/42)sin(100002/43)cos(100002/40)cos(100002/41)cos(100002/42)cos(100002/43)=0.11.0411.2090.5411.20.84−0.016−0.4890.30.410.520.8951.41.491.591.655\n我们可以使用如下的代码来获取上述例子的位置编码：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import numpy as np import matplotlib.pyplot as plt def PositionEncoding(seq_len, d_model, n=10000): P = np.zeros((seq_len, d_model)) for k in range(seq_len): for i in np.arange(int(d_model/2)): denominator = np.power(n, 2*i/d_model) P[k, 2*i] = np.sin(k/denominator) P[k, 2*i+1] = np.cos(k/denominator) return P P = PositionEncoding(seq_len=4, d_model=4, n=100) print(P)Copy to clipboardErrorCopied [[ 0. 1. 0. 1. ] [ 0.84147098 0.54030231 0.09983342 0.99500417] [ 0.90929743 -0.41614684 0.19866933 0.98006658] [ 0.14112001 -0.9899925 0.29552021 0.95533649]] 这样的位置编码主要有两个好处：\n使 PE 能够适应比训练集里面所有句子更长的句子，假设训练集里面最长的句子是有 20 个单词，突然来了一个长度为 21 的句子，则使用公式计算的方法可以计算出第 21 位的 Embedding。 可以让模型容易地计算出相对位置，对于固定长度的间距 k，PE(pos+k) 可以用 PE(pos) 计算得到。因为 Sin(A+B) = Sin(A)Cos(B) + Cos(A)Sin(B), Cos(A+B) = Cos(A)Cos(B) - Sin(A)Sin(B)。 上面那个好复杂,还是代码看着顺眼\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class PositionalEncoding(nn.Module): \u0026#39;\u0026#39;\u0026#39;位置编码模块\u0026#39;\u0026#39;\u0026#39; def __init__(self, args): super(PositionalEncoding, self).__init__() # Dropout 层 # self.dropout = nn.Dropout(p=args.dropout) # block size 是序列的最大长度 pe = torch.zeros(args.block_size, args.n_embd) position = torch.arange(0, args.block_size).unsqueeze(1) # 计算 theta div_term = torch.exp( torch.arange(0, args.n_embd, 2) * -(math.log(10000.0) / args.n_embd) ) # 分别计算 sin、cos 结果 pe[:, 0::2] = torch.sin(position * div_term) pe[:, 1::2] = torch.cos(position * div_term) pe = pe.unsqueeze(0) self.register_buffer(\u0026#34;pe\u0026#34;, pe) def forward(self, x): # 将位置编码加到 Embedding 结果上 x = x + self.pe[:, : x.size(1)].requires_grad_(False) return x 2.3.3 一个完整的Transformer 但需要注意的是，上图是原论文《Attention is all you need》配图，LayerNorm 层放在了 Attention 层后面，也就是“Post-Norm”结构，但在其发布的源代码中，LayerNorm 层是放在 Attention 层前面的，也就是“Pre Norm”结构。考虑到目前 LLM 一般采用“Pre-Norm”结构（可以使 loss 更稳定），本文在实现时采用“Pre-Norm”结构。\n如图，经过 tokenizer 映射后的输出先经过 Embedding 层和 Positional Embedding 层编码，然后进入上一节讲过的 N 个 Encoder 和 N 个 Decoder（在 Transformer 原模型中，N 取为6），最后经过一个线性层和一个 Softmax 层就得到了最终输出。\n1 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 class Transformer(nn.Module): \u0026#39;\u0026#39;\u0026#39;整体模型\u0026#39;\u0026#39;\u0026#39; def __init__(self, args): super().__init__() # 必须输入词表大小和 block size assert args.vocab_size is not None assert args.block_size is not None self.args = args self.transformer = nn.ModuleDict(dict( wte = nn.Embedding(args.vocab_size, args.n_embd), wpe = PositionalEncoding(args), drop = nn.Dropout(args.dropout), encoder = Encoder(args), decoder = Decoder(args), )) # 最后的线性层，输入是 n_embd，输出是词表大小 self.lm_head = nn.Linear(args.n_embd, args.vocab_size, bias=False) # 初始化所有的权重 self.apply(self._init_weights) # 查看所有参数的数量 print(\u0026#34;number of parameters: %.2fM\u0026#34; % (self.get_num_params()/1e6,)) \u0026#39;\u0026#39;\u0026#39;统计所有参数的数量\u0026#39;\u0026#39;\u0026#39; def get_num_params(self, non_embedding=False): # non_embedding: 是否统计 embedding 的参数 n_params = sum(p.numel() for p in self.parameters()) # 如果不统计 embedding 的参数，就减去 if non_embedding: n_params -= self.transformer.wte.weight.numel() return n_params \u0026#39;\u0026#39;\u0026#39;初始化权重\u0026#39;\u0026#39;\u0026#39; def _init_weights(self, module): # 线性层和 Embedding 层初始化为正则分布 if isinstance(module, nn.Linear): torch.nn.init.normal_(module.weight, mean=0.0, std=0.02) if module.bias is not None: torch.nn.init.zeros_(module.bias) elif isinstance(module, nn.Embedding): torch.nn.init.normal_(module.weight, mean=0.0, std=0.02) \u0026#39;\u0026#39;\u0026#39;前向计算函数\u0026#39;\u0026#39;\u0026#39; def forward(self, idx, targets=None): # 输入为 idx，维度为 (batch size, sequence length, 1)；targets 为目标序列，用于计算 loss device = idx.device b, t = idx.size() assert t \u0026lt;= self.args.block_size, f\u0026#34;不能计算该序列，该序列长度为 {t}, 最大序列长度只有 {self.args.block_size}\u0026#34; # 通过 self.transformer # 首先将输入 idx 通过 Embedding 层，得到维度为 (batch size, sequence length, n_embd) print(\u0026#34;idx\u0026#34;,idx.size()) # 通过 Embedding 层 tok_emb = self.transformer.wte(idx) print(\u0026#34;tok_emb\u0026#34;,tok_emb.size()) # 然后通过位置编码 pos_emb = self.transformer.wpe(tok_emb) # 再进行 Dropout x = self.transformer.drop(pos_emb) # 然后通过 Encoder print(\u0026#34;x after wpe:\u0026#34;,x.size()) enc_out = self.transformer.encoder(x) print(\u0026#34;enc_out:\u0026#34;,enc_out.size()) # 再通过 Decoder x = self.transformer.decoder(x, enc_out) print(\u0026#34;x after decoder:\u0026#34;,x.size()) if targets is not None: # 训练阶段，如果我们给了 targets，就计算 loss # 先通过最后的 Linear 层，得到维度为 (batch size, sequence length, vocab size) logits = self.lm_head(x) # 再跟 targets 计算交叉熵 loss = F.cross_entropy(logits.view(-1, logits.size(-1)), targets.view(-1), ignore_index=-1) else: # 推理阶段，我们只需要 logits，loss 为 None # 取 -1 是只取序列中的最后一个作为输出 logits = self.lm_head(x[:, [-1], :]) # note: using list [-1] to preserve the time dim loss = None return logits, loss 在前向计算函数中，我们对模型使用 pytorch 的交叉熵函数来计算损失\n","permalink":"https://tuanzi-wow.cn/llm/llm-transformer/","summary":"\u003ch2 id=\"transformer框架\"\u003eTransformer框架\u003c/h2\u003e\n\u003ch2 id=\"21-注意力机制\"\u003e2.1 注意力机制\u003c/h2\u003e\n\u003ch3 id=\"211-什么是注意力机制\"\u003e2.1.1 什么是注意力机制\u003c/h3\u003e\n\u003cp\u003e从计算机视觉为起源发展起来的神经网络,其核心架构有三种\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e前馈神经网络（Feedforward Neural Network，FNN）\u003c/strong\u003e，即数据从输入层单向流动到输出层，无循环结构，各层之间通过全连接或特定方式传递信息，其中多层感知机（Multi-Layer Perceptron，MLP）是最常见的形式，每一层的神经元都和上下两层的每一个神经元完全连接，如图2.1所示:\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\u003cimg alt=\"图片描述\" loading=\"lazy\" src=\"https://raw.githubusercontent.com/datawhalechina/happy-llm/main/docs/images/2-figures/1-0.png\"\u003e\u003c/p\u003e","title":"Transformer 架构"},{"content":"Skills \u0026amp; Plugins Helps to complete repeatable work with the right instructions,resources,and tools,They reduce the need to paste the same prompt ,template,requirements,or process into every chat.\nSkill: A skill packages instructions and supporting resources for a specific task or workflow.\nPlugin: A plugin is an installable bundle that can include skills, connectors, or both. Connectors are backed by Model Context Protocol (MCP) servers and can optionally include custom ChatGPT UI.\nUse skills for repeatable work: A skill is a reusable workflow that gives ChatGPT or Codex task-specific guidance.\nA skill can combine:\nA name and description that help ChatGPT and Codex recognize when the skill applies. Workflow instructions that define the process and expected result. Supporting resources such as templates, examples, brand guidance, schemas, or connected tools. Build skills: You can start by turning a task you already repeat into a focused playbook for ChatGPT and Codex.\nTo build a useful skill:\nChoose one focused task. Note what you normally start with, such as files, links, or notes, and what a finished result should look like. Describe the workflow. In ChatGPT, start with @skill-creator; in Codex, use $skill-creator. Explain the goal, the steps to follow, the expected format, and anything the skill should always include or avoid. Add a template or a good example when you have one. Review and try the draft. Check the instructions, test the skill with a realistic request, and refine it if the result misses a step or drifts from the format you want. Install and reuse it. Once the skill is enabled, ChatGPT or Codex can use it for relevant requests, or you can select it explicitly. You can also share it with teammates when your workspace settings allow it. Use plugins for tools and shared workflows: A plugin can combine skills with connectors for services such as GitHub, Google Drive, or Slack, and can include MCP servers for additional tools and context.\nChoose between a skill and a plugin: Use a skill when you need reusable instructions for a focused task.\nUse a plugin when you want an installable package that can combine instructions with connected services or other tools.\n","permalink":"https://tuanzi-wow.cn/codex/codex-skills-plugins/","summary":"\u003ch2 id=\"skills--plugins\"\u003eSkills \u0026amp; Plugins\u003c/h2\u003e\n\u003cp\u003eHelps to complete repeatable work with the right instructions,resources,and tools,They reduce the need to paste the same prompt ,template,requirements,or process into every chat.\u003c/p\u003e\n\u003ch4 id=\"skill\"\u003eSkill:\u003c/h4\u003e\n\u003cp\u003eA \u003cstrong\u003eskill\u003c/strong\u003e packages instructions and supporting resources for a specific task or workflow.\u003c/p\u003e\n\u003ch4 id=\"plugin\"\u003ePlugin:\u003c/h4\u003e\n\u003cp\u003eA \u003cstrong\u003eplugin\u003c/strong\u003e is an installable bundle that can include skills, connectors, or both. Connectors are backed by Model Context Protocol (MCP) servers and can optionally include custom ChatGPT UI.\u003c/p\u003e","title":"Skills 与 Plugins"},{"content":"Context windows Understand how the context window works, how extended thinking and tool use count toward it, and how to manage context as conversations grow.\nthe primary strategy is server-side-compaction\nHow the context window works what is the context window: The \u0026ldquo;context window\u0026rdquo; refers to all the text a language model can reference when generating a response, including the response itself. https://platform.claude.com/docs/images/context-window.svg\nAs token count grows, accuracy and recall degrade, a phenomenon known as context rot.\nChat interfaces manage the context window on a rolling \u0026ldquo;first in,first out \u0026ldquo;basis\nProgressive token accumulation: user message and assistant response accumulates within the context window and previous is preserved Context window capacity: Input-Output flow: Each turn consists of input phase output phase Everything in the turns counts:\nthe system prompt,every message in messages(including tool results,images, and documents) and your tool definition the output LLM generates, including the extended thinking If you use prompt caching, the input count is split across input_tokens, cache_read_input_tokens, and cache_creation_input_tokens\nContext window sizes by model the max length of context window is up to the model\nThe context window with thinking Thinking tokens are a subset of your max_tokens parameter, are billed as output tokens . Whether thinking blocks from previous assistant turns stay in the context window depends on the model. Most of LLMs keep previous thinking blocks by default, and they count toward the context window like any other input tokens.\nThe following diagram shows how tokens are managed when thinking is enabled on a model that strips previous thinking blocks: https://platform.claude.com/docs/images/context-window-thinking.svg\nBilling: Thinking tokens are billed as output tokens once, when they are generated. On models that keep previous thinking blocks, the kept blocks are then part of later requests\u0026rsquo; input and are billed as input tokens, like the rest of the conversation history.\nThe context with thinking and tool use The following diagram illustrates how tokens are managed when you combine thinking with tool use on a model that strips previous thinking blocks: https://platform.claude.com/docs/images/context-window-thinking-tools.svg\nA little bit different from my previous thought\nFirst turn architecture Input components: Tools configuration and user message Output components: Thinking + text response + tool use request Token calculation: All input and output components count toward the context window, and all output components are billed as output tokens. Tool result handling (turn 2)\nInput components: Every block in the first turn and the tool_result. You must return the thinking block with the corresponding tool results. This is the only case where you have to return thinking blocks. Output components: After tool results have been passed back to Claude, Claude responds with only text (no additional thinking until the next user message, unless interleaved thinking is enabled). Token calculation: All input and output components count toward the context window, and all output components are billed as output tokens. New user turn (turn 3)\nInput components: All inputs and the output from the previous turn are carried forward. The thinking block from the completed tool use cycle no longer has to stay in context: on models that strip previous thinking blocks, the API drops it automatically when you pass it back, and on models that keep previous thinking blocks, it stays unless you clear it with thinking block clearing. This is also where you add the next user turn. Output components: Because there is a new user turn outside the tool use cycle, Claude generates a new thinking block and continues from there. Token calculation: On models that strip previous thinking blocks, the previous thinking tokens no longer count toward the context window. All other previous blocks still count toward the context window, as does the thinking block in the current assistant turn. Interleaved thinking: Let LLMs think between tool calls.,including after it receives tool results\nContext awareness these models track their remaining context window (their \u0026ldquo;token budget\u0026rdquo;) throughout a conversation.\nHow it works In the system prompt of every request, the API gives Claude its total context window:\n1 \u0026lt;budget:token_budget\u0026gt;200000\u0026lt;/budget:token_budget\u0026gt; After each tool call, the API gives Claude an update on its remaining capacity:\n1 \u0026lt;system_warning\u0026gt;Token usage: 35000/200000; 165000 remaining\u0026lt;/system_warning\u0026gt; For agents that span multiple sessions, design your state artifacts so that context recovery is fast when a new session starts. The memory tool\u0026rsquo;s multisession pattern walks through a concrete approach. See also Effective harnesses for long-running agents.\nManage context with compaction If your conversations regularly approach context window limits, use server-side compaction. Compaction automatically summarizes earlier parts of the conversation on the server, so the conversation can continue past the context window limit. It is available in beta for Claude 4.6 and later models and Claude Mythos Preview.\nTool result clearing: Clear old tool results in agentic workflows Thinking block clearing: Manage thinking blocks when you use extended thinking Context window overflow behavior If the input alone already exceeds the model\u0026rsquo;s context window, the API returns a 400 invalid_request_error (\u0026ldquo;prompt is too long\u0026rdquo;) on every model.\nOn Claude 4.5 models and newer, if input tokens plus max_tokens exceeds the context window size, the API accepts the request. If generation then reaches the context window limit, it stops with stop_reason: \u0026quot;model_context_window_exceeded\u0026quot;\nOn earlier models, the API returns a validation error instead.\n","permalink":"https://tuanzi-wow.cn/%E4%B8%8A%E4%B8%8B%E6%96%87%E5%B7%A5%E7%A8%8B/claude_context_management/context-windows/","summary":"\u003ch1 id=\"context-windows\"\u003eContext windows\u003c/h1\u003e\n\u003cp\u003eUnderstand how the context window works, how extended thinking and tool use count toward it, and how to manage context as conversations grow.\u003c/p\u003e\n\u003cp\u003ethe primary strategy is \u003cstrong\u003eserver-side-compaction\u003c/strong\u003e\u003c/p\u003e\n\u003ch2 id=\"how-the-context-window-works\"\u003eHow the context window works\u003c/h2\u003e\n\u003ch3 id=\"what-is-the-context-window\"\u003ewhat is the context window:\u003c/h3\u003e\n\u003cp\u003eThe \u0026ldquo;context window\u0026rdquo; refers to all the text a language model can reference when generating a response, \u003cstrong\u003eincluding the response itself.\u003c/strong\u003e\n\u003ca href=\"https://platform.claude.com/docs/images/context-window.svg\"\u003ehttps://platform.claude.com/docs/images/context-window.svg\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003eAs token count grows, accuracy and recall degrade, a phenomenon known as \u003cstrong\u003e\u003cem\u003econtext rot\u003c/em\u003e.\u003c/strong\u003e\u003c/p\u003e","title":"Context Management · Context Windows"},{"content":"1.相交链表 题目描述：给你两个单链表的头节点 headA 和 headB ，请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点，返回 null 。\n图示两个链表在节点 c1 开始相交**：**\n题目数据 保证 整个链式结构中不存在环。\n注意，函数返回结果后，链表必须 保持其原始结构 。\n自定义评测：\n评测系统 的输入如下（你设计的程序 不适用 此输入）：\nintersectVal - 相交的起始节点的值。如果不存在相交节点，这一值为 0 listA - 第一个链表 listB - 第二个链表 skipA - 在 listA 中（从头节点开始）跳到交叉节点的节点数 skipB - 在 listB 中（从头节点开始）跳到交叉节点的节点数 评测系统将根据这些输入创建链式数据结构，并将两个头节点 headA 和 headB 传递给你的程序。如果程序能够正确返回相交节点，那么你的解决方案将被 视作正确答案 。\n示例 1：\n1 2 3 4 5 6 输入：intersectVal = 8, listA = [4,1,8,4,5], listB = [5,6,1,8,4,5], skipA = 2, skipB = 3 输出：Intersected at \u0026#39;8\u0026#39; 解释：相交节点的值为 8 （注意，如果两个链表相交则不能为 0）。 从各自的表头开始算起，链表 A 为 [4,1,8,4,5]，链表 B 为 [5,6,1,8,4,5]。 在 A 中，相交节点前有 2 个节点；在 B 中，相交节点前有 3 个节点。 — 请注意相交节点的值不为 1，因为在链表 A 和链表 B 之中值为 1 的节点 (A 中第二个节点和 B 中第三个节点) 是不同的节点。换句话说，它们在内存中指向两个不同的位置，而链表 A 和链表 B 中值为 8 的节点 (A 中第三个节点，B 中第四个节点) 在内存中指向相同的位置。 示例 2：\n1 2 3 4 5 输入：intersectVal = 2, listA = [1,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1 输出：Intersected at \u0026#39;2\u0026#39; 解释：相交节点的值为 2 （注意，如果两个链表相交则不能为 0）。 从各自的表头开始算起，链表 A 为 [1,9,1,2,4]，链表 B 为 [3,2,4]。 在 A 中，相交节点前有 3 个节点；在 B 中，相交节点前有 1 个节点。 示例 3：\n1 2 3 4 5 输入：intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2 输出：No intersection 解释：从各自的表头开始算起，链表 A 为 [2,6,4]，链表 B 为 [1,5]。 由于这两个链表不相交，所以 intersectVal 必须为 0，而 skipA 和 skipB 可以是任意值。 这两个链表不相交，因此返回 null 。 题解：挺有意思的，这样不停循环总有一次会交汇的 1 2 3 4 5 6 7 8 class Solution(object): def getIntersectionNode(self, headA, headB): pa = headA pb = headB while pa != pb: pa = pa.next if pa else headB pb = pb.next if pb else headA return pa 2.反转链表 给你单链表的头节点 head ，请你反转链表，并返回反转后的链表。\n示例 1：\n1 2 输入：head = [1,2,3,4,5] 输出：[5,4,3,2,1] 示例 2：\n1 2 输入：head = [1,2] 输出：[2,1] 示例 3：\n1 2 输入：head = [] 输出：[] 题解:可以说是经典解法了 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 # Definition for singly-linked list. # class ListNode: # def __init__(self, val=0, next=None): # self.val = val # self.next = next class Solution: def reverseList(self, head: Optional[ListNode]) -\u0026gt; Optional[ListNode]: pre=None cur = head while cur: next_node = cur.next cur.next = pre pre = cur cur = next_node return pre 3.回文链表 题目描述 给你一个单链表的头节点 head ，请你判断该链表是否为回文链表。如果是，返回 true ；否则，返回 false\n题解：方法都是转化成value判断，看谁写的效率高罢了\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 # Definition for singly-linked list. # class ListNode: # def __init__(self, val=0, next=None): # self.val = val # self.next = next class Solution: def isPalindrome(self, head: Optional[ListNode]) -\u0026gt; bool: values = [] cur = head cnt = 0 while cur: values.append(cur.val) cur = cur.next cnt += 1 for i in range((int)(cnt/2)): if values[i] != values[cnt-i-1]: return False return True 4.环形链表 题目描述 给你一个链表的头节点 head ，判断链表中是否有环。\n如果链表中有某个节点，可以通过连续跟踪 next 指针再次到达，则链表中存在环。 为了表示给定链表中的环，评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置（索引从 0 开始）。注意：pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。\n如果链表中存在环 ，则返回 true 。 否则，返回 false 。\n题解 1.快慢指针法：顾名思义，我懒得解释\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 # Definition for singly-linked list. # class ListNode: # def __init__(self, x): # self.val = x # self.next = None class Solution: def hasCycle(self, head: Optional[ListNode]) -\u0026gt; bool: slow = head fast = head while fast and fast.next: slow = slow.next fast = fast.next.next if slow == fast: return True return False 2.嗨嗨嗨，我用集合想不到吧set来了\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 # Definition for singly-linked list. # class ListNode: # def __init__(self, x): # self.val = x # self.next = None class Solution: def hasCycle(self, head: Optional[ListNode]) -\u0026gt; bool: cur = head visit =set() while cur: if cur in visit: return True visit.add(cur) cur = cur.next return False 5.环形链表2 题目描述 给定一个链表的头节点 head ，返回链表开始入环的第一个节点。 如果链表无环，则返回 null。\n如果链表中有某个节点，可以通过连续跟踪 next 指针再次到达，则链表中存在环。 为了表示给定链表中的环，评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置（索引从 0 开始）。如果 pos 是 -1，则在该链表中没有环。注意：pos 不作为参数进行传递，仅仅是为了标识链表的实际情况。\n不允许修改 链表。\n题解：还是上面两种 set\n1 2 3 4 5 6 7 8 9 10 11 12 13 class Solution: def detectCycle(self, head): visited = set() while head: if head in visited: return head visited.add(head) head = head.next return None 快慢指针：懒得找了，比较经典吧\n6.合并两个有序列表 题目描述 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的\n题解：虚拟头节点：挺有思想的，值得学习；我当时忘记处理剩下的剩余链表了\n1 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 class Solution: def mergeTwoLists( self, list1: Optional[ListNode], list2: Optional[ListNode] ) -\u0026gt; Optional[ListNode]: dummy = ListNode() cur = dummy while list1 and list2: if list1.val \u0026lt;= list2.val: cur.next = list1 list1 = list1.next else: cur.next = list2 list2 = list2.next cur = cur.next if list1: cur.next = list1 else: cur.next = list2 return dummy.next 7.again 两数相加 题目描述： 给你两个 非空 的链表，表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的，并且每个节点只能存储 一位 数字。\n请你将两个数相加，并以相同形式返回一个表示和的链表。\n你可以假设除了数字 0 之外，这两个数都不会以 0 开头。\n题解： 模拟竖式加法，逐位计算两个链表节点的值，加上进位，然后用尾插法把每一位结果连接成新的链表。\n1 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 # Definition for singly-linked list. # class ListNode: # def __init__(self, val=0, next=None): # self.val = val # self.next = next class Solution: def addTwoNumbers(self, l1: Optional[ListNode], l2: Optional[ListNode]) -\u0026gt; Optional[ListNode]: dummy = ListNode() cur = dummy carry = 0 while l1 or l2 or carry: val1 = l1.val if l1 else 0 val2 = l2.val if l2 else 0 total = val1 + val2 + carry carry = total // 10 digit = total % 10 cur.next = ListNode(digit) cur = cur.next if l1: l1 = l1.next if l2: l2 = l2.next return dummy.next 8.删除链表倒数第n个节点 题目描述： 给你一个链表，删除链表的倒数第 n 个结点，并且返回链表的头结点。\n题解： 依旧快慢指针这一块\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 # Definition for singly-linked list. # class ListNode: # def __init__(self, val=0, next=None): # self.val = val # self.next = next class Solution: def removeNthFromEnd(self, head: Optional[ListNode], n: int) -\u0026gt; Optional[ListNode]: dummy = ListNode(0,head) slow,fast = dummy,dummy for i in range(n): fast = fast.next while fast.next: fast = fast.next slow = slow.next slow.next = slow.next.next return dummy.next 9.两两交换链表中的结点 题目描述： 给你一个链表，两两交换其中相邻的节点，并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题（即，只能进行节点交换）。\n题解： 使用虚拟头节点 dummy 和一个前驱指针 pre，每次找到相邻的两个节点进行交换，然后把 pre 移动到交换后的第一个节点（原来的第一个节点），继续处理下一对\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution: def swapPairs(self, head: Optional[ListNode]) -\u0026gt; Optional[ListNode]: dummy = ListNode(0, head) pre = dummy while pre.next and pre.next.next: first = pre.next second = first.next # 交换两个节点 first.next = second.next second.next = first pre.next = second # 移动到下一组 pre = first return dummy.next 10.K个一组翻转链表 题目描述： 给你链表的头节点 head ，每 k 个节点一组进行翻转，请你返回修改后的链表。\nk 是一个正整数，它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍，那么请将最后剩余的节点保持原有顺序。\n你不能只是单纯的改变节点内部的值，而是需要实际进行节点交换。\n题解： 啊，真是一场酣畅淋漓的战斗啊\n1 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 class Solution: def reverseKGroup(self, head: Optional[ListNode], k: int) -\u0026gt; Optional[ListNode]: dummy = ListNode() dummy.next = head groupprv = dummy while True: kth = groupprv # 找第 k 个节点 for _ in range(k): kth = kth.next if not kth: return dummy.next groupnext = kth.next # 翻转当前组 cur = groupprv.next tail = groupnext while cur != groupnext: nxt = cur.next cur.next = tail tail = cur cur = nxt # 更新连接关系 tmp = groupprv.next groupprv.next = kth groupprv = tmp 11.排序列表 题目描述： 给你链表的头结点 head ，请将其按 升序 排列并返回 排序后的链表 。\n题解 1.我要暴力了啊：冒泡排序 class Solution: def sortList(self, head: Optional[ListNode]) -\u0026gt; Optional[ListNode]: if not head or not head.next: return head\nclass Solution: def sortList(self, head: Optional[ListNode]) -\u0026gt; Optional[ListNode]: if not head or not head.next: return head # 计算链表长度 cnt = 0 cur = head while cur: cnt += 1 cur = cur.next # 冒泡排序 for i in range(cnt - 1): cur = head for j in range(cnt - 1 - i): if cur.val \u0026gt; cur.next.val: cur.val, cur.next.val = cur.next.val, cur.val cur = cur.next return head 2.归并排序 不断把链表从中间拆成两半，直到每个链表只剩一个节点；然后再将两个有序链表不断合并，最终得到完整的有序链表。 递归；归并排序 思路很重要，代码挺简单的，自己看\n1 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 class Solution: def sortList(self, head: Optional[ListNode]) -\u0026gt; Optional[ListNode]: # 只有 0 个或 1 个节点，不需要排序 if not head or not head.next: return head # ==================== # 1. 快慢指针找中点 # ==================== slow = head fast = head.next while fast and fast.next: slow = slow.next fast = fast.next.next # ==================== # 2. 从中间断开 # ==================== mid = slow.next slow.next = None # ==================== # 3. 分别排序左右两部分 # ==================== left = self.sortList(head) right = self.sortList(mid) # ==================== # 4. 合并两个有序链表 # ==================== return self.merge(left, right) def merge(self, l1, l2): dummy = ListNode() cur = dummy while l1 and l2: if l1.val \u0026lt; l2.val: cur.next = l1 l1 = l1.next else: cur.next = l2 l2 = l2.next cur = cur.next # 剩下的直接接上 if l1: cur.next = l1 else: cur.next = l2 return dummy.next 3.一个为了解题的简单方法，挺没意思的\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Solution: def sortList(self, head: Optional[ListNode]) -\u0026gt; Optional[ListNode]: if not head or not head.next: return head seen = [] cur = head while cur: seen.append(cur.val) cur = cur.next seen.sort() cur = head for i in seen: cur.val = i cur = cur.next return head 12.合并k个升序列表 题目描述： 给你一个链表数组，每个链表都已经按升序排列。\n请你将所有链表合并到一个升序链表中，返回合并后的链表。\n题解： 不道德的解法1：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 # Definition for singly-linked list. # class ListNode: # def __init__(self, val=0, next=None): # self.val = val # self.next = next class Solution: def mergeKLists(self, lists: List[Optional[ListNode]]) -\u0026gt; Optional[ListNode]: values=[] for head in lists: while head: values.append(head.val) head=head.next values.sort() ptr = ListNode() dummy = ListNode() dummy.next = ptr for x in range(len(values)): cur = ListNode(values[x]) ptr.next = cur ptr=cur return dummy.next.next 13.LRU缓存 题目描述： 请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。\n实现 LRUCache 类：\nLRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存 int get(int key) 如果关键字 key 存在于缓存中，则返回关键字的值，否则返回 -1 。 void put(int key, int value) 如果关键字 key 已经存在，则变更其数据值 value ；如果不存在，则向缓存中插入该组 key-value 。如果插入操作导致关键字数量超过 capacity ，则应该 逐出 最久未使用的关键字。 函数 get 和 put 必须以 O(1) 的平均时间复杂度运行。\n解法：哈希表加双向链表或者你熟悉LinkedHashmap也好做 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 class Node: def __init__(self, key=0, value=0): self.key = key self.value = value self.prev = None self.next = None class LRUCache: def __init__(self, capacity: int): self.capacity = capacity # 哈希表 self.cache = {} # 双向链表虚拟头尾 self.head = Node() self.tail = Node() self.head.next = self.tail self.tail.prev = self.head def get(self, key: int) -\u0026gt; int: if key not in self.cache: return -1 node = self.cache[key] # 最近使用，移动到头部 self._remove(node) self._add_to_head(node) return node.value def put(self, key: int, value: int) -\u0026gt; None: # key 已经存在 if key in self.cache: node = self.cache[key] node.value = value # 更新后也是最近使用 self._remove(node) self._add_to_head(node) else: # 创建新节点 node = Node(key, value) self.cache[key] = node # 放到头部 self._add_to_head(node) # 超过容量 if len(self.cache) \u0026gt; self.capacity: # 删除尾部节点 last = self.tail.prev self._remove(last) del self.cache[last.key] def _remove(self, node): prev_node = node.prev next_node = node.next prev_node.next = next_node next_node.prev = prev_node def _add_to_head(self, node): node.prev = self.head node.next = self.head.next self.head.next.prev = node self.head.next = node ","permalink":"https://tuanzi-wow.cn/leetcode%E5%88%B7%E9%A2%98%E8%AE%B0%E5%BD%95/hot100/hot100-linked-list/","summary":"\u003ch3 id=\"1相交链表\"\u003e1.相交链表\u003c/h3\u003e\n\u003cp\u003e题目描述：给你两个单链表的头节点 \u003ccode\u003eheadA\u003c/code\u003e 和 \u003ccode\u003eheadB\u003c/code\u003e ，请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点，返回 \u003ccode\u003enull\u003c/code\u003e 。\u003c/p\u003e\n\u003cp\u003e图示两个链表在节点 \u003ccode\u003ec1\u003c/code\u003e 开始相交**：**\u003c/p\u003e","title":"HOT100 · 链表"},{"content":"第三章 预训练语言模型(PLM) 3.1 Encoder-only PLM 3.1.1 BERT( Bidirectional Encoder Representations from Transformers) 由Google发布的,引领了预训练+微调的的潮流\n(1)思想沿承 统一了多种思想的预训练框架,包括: Transformer架构: 通过在Transformer的模型基座上进行优化,将Encoder的结构进行堆叠,扩大模型参数 预训练+微调范式: ELMo模型基于LSTM双向架构 而BERT则将模型框架调整为了Transformer,引入了更适合文本理解,能捕捉深层双向语义的预训练任务MLM\n接下来从模型架构,预训练任务,下游任务来深入剖析BERT\n(2)模型架构 采取了将Transformer的Encoder部分堆叠而成\n图片描述\n其输入一般是文本序列,输出一般是Label\n分类头:predictions_heads: 将多维度的隐藏状态从线性层转化到分类维度\n模型整体上是由Embedding,Encoder,prediction_heads组成的\n图片描述\n输入文本-\u0026gt;tokenizer-\u0026gt;input_ids-\u0026gt;Embedding-\u0026gt;hidden_states-\u0026gt;Encoder-\u0026gt;最顶层hidden_states-\u0026gt;predictions_heads 其中Encoder块中,BERT由有两种规模的模型,分别是: base版本:12层Encoder layer 768的隐藏层维度 总参数量110M lager版本:24层Encoder layer 1024隐藏层维度从总参数数量340M\nBERT的分词方法: WordPiece 基于统计的子词切分算法,核心在于把单词拆解为子词,其合并操作的依据所示最大化语言模型的似然度,对于中文等非空格分割的语言通常采用单个汉字作为token处理\nprediction_heads其实就是线性层加上激活函数\n图片描述\n而每一层Encoder layer都是和Transformer中的Encoder结构相似的层 图片描述\n通过 Embedding 层映射的 hidden_states 进入核心的 attention 机制，然后通过残差连接的机制和原输入相加，再经过一层 Intermediate 层得到最终输出\n采用的激活函数:GELU函数,全名为高斯误差线性单元激活函数 起计算方式为: $$ GELU(x)=0.5x(1+tanh( π/ 2 ​\n​ )(x+0.044715x 3 )) $$ 核心思路L将随机正则的思想引入激活函数,通过输入自身的概率分布,来决定抛弃还是保留自己的神经元\nBERT的注意力机制和Transformer几乎完全一致,但是BERT的相对位置编码融合在了记忆力机制中,将相对位置编码同样是为可以训练的权重参数 图片描述\nBERT 的注意力计算过程和 Transformer 的唯一差异在于，在完成注意力分数的计算之后，先通过 Position Embedding 层来融入相对位置信息。这里的 Position Embedding 层，其实就是一层线性矩阵。通过可训练的参数来拟合相对位置，相对而言比 Transformer 使用的绝对位置编码 Sinusoidal 能够拟合更丰富的相对位置信息，但是，这样也增加了不少模型参数，同时完全无法处理超过模型训练长度的输入 注解:最开始BERT采用的也是类似于Transformer的绝对位置编码,后续改进为相对位置编码\n(3)预训练任务\u0026ndash;MLM+NSP 创新点出现了!:新的两个预训练任务MLM+NSP(Next Sentence Prediction,下一句预测) 预训练+微调的范式的优越性在于: 完成预训练的模型可以仅通过微调应用在所有的下游任务当中 因此:可以进一步扩大模型参数和预训练数据量让模型拟合潜在语义和底层知识\n预训练数据的核心要求是需要极大的数据规模.通过人工标注产出的全监督数据很难达到这个规模。因此，预训练数据一定是从无监督的语料中获取\n这也是为什么传统的预训练任务都是 LM 的原因——LM 使用上文预测下文的方式可以直接应用到任何文本中，对于任意文本，我们只需要将下文遮蔽将上文输入模型要求其预测就可以实现 LM 训练，因此互联网上所有文本语料都可以被用于预训练\nLM预训练任务的缺陷: 直接拟合从左到右的语义关系.而忽略了双向的语音关系哦\n因此,引入了MLM,第一个M的含义是MASK(掩码) 其思路为:在文本序列中随机遮蔽部分token,将没有被遮蔽的token输入模型中,要求模型根据输入预测被遮蔽的token. 可以同时利用上文和下文一起理解语义来预测被遮蔽的token\nMLM的固有缺陷: 训练和下游任务应该是保持一致的,但是MLM不同,下游任务微调和推理的时候,是不存在我们人工加入的 预训练和微调不一致会严重影响下游任务微调的性能\nMLM的策略改进:(我去真的好聪明) 选择训练语料中的15%进行遮蔽,其中并不是全部都进行遮蔽,其中 80%进行遮蔽:进行MASK训练 10%被替换为任意一个token:让模型无法确定需要预测的token,从而保持每一个 token的上下文表征分布 10%保持不变:消除预训练和微调的不一致 且由于10%随机替换的概率很低,并不会影响模型实质语言理解能力\nNSP: 核心思想是针对句级的NLU任务\n自然语言推理是指，输入一个前提和一个推理，判断推理是否是符合前提的。这样的任务都需要模型在句级去拟合关系，判断两个句子之间的关系，而不仅是 MLM 在 token 级拟合的语义关系\nNSP的核心思路是要求模型判断一个句对的两个句子是否是连续的上下文.例如:\n1 2 3 4 5 6 7 8 9 10 11 输入： Sentence A：I love you. Sentence B: Because you are wonderful. 输出： 1（是连续上下文） 输入： Sentence A：I love you. Sentence B: Because today\u0026#39;s dinner is so nice. 输出： 0（不是连续上下文） 通过要求模型判断句对关系,迫使模型拟合句子之间的关系,来适配句级的NLU任务 正样本可以从无监督语料中抽取连续的句子,负样本对句子打乱后随机抽取,因此可以拥有几乎无限的训练数据\n(4)下游任务微调 预训练-微调的重点在于: 通过预训练获得的强大能力是否通过低成本的微调快速迁移到对应的下游任务中\n策略: 设计了更加通用的输入和输出层来设配多任务下的迁移学习: 对于每一个输入的文本序列,会在首部加入一个特殊的token代表整句的状态,也就是句级的语义特征.在进行NSP预训练时,就使用该token对应的特征向量来作为最后分类器的输入\n在完成预训练后，针对每一个下游任务，只需要使用一定量的全监督人工标注数据，对预训练的 BERT 在该任务上进行微调即可。\n对于某些特定、训练数据丰富且强调高吞吐的任务，BERT 比 LLM 更具有可用性\n3.1.2 RoBERTa 经过BERT优化而来的\n(1)优化一: 去掉NSP预训练任务 RoBERTa使用BERT的lager框架 实验结果表明:NSP 任务并不能提高模型性能，因为其太过简单，加入到预训练中并不能使下游任务微调时明显受益，甚至会带来负面效果。\n对于MLM任务的优化: 动态遮蔽策略: RoBERTa 对 MLM 任务本身也做出了改进。在 BERT 中，Mask 的操作是在数据处理的阶段完成的，因此后期预训练时同一个 sample 待预测的 \u0026lt;MASK\u0026gt; 总是一致的。由于 BERT 共训练了 40 个 Epoch，为使模型的训练数据更加广泛，BERT 将数据进行了四次随机 Mask，也就是每 10个 Epoch 模型训练的数据是完全一致的。而 RoBERTa 将 Mask 操作放到了训练阶段，也就是动态遮蔽策略，从而让每一个 Epoch 的训练数据 Mask 的位置都不一致。\n训练效果优势不明显,但是高效且易于实现,后续更多都采用动态遮蔽\n(2)优化二: 更大的bpe词表 Tokenizer的编码策略不同:使用了BPE的编码策略, 即Byte Pair Encoding 字节对编码,以子词对作为分词的单位,例如: 对“Hello World”这句话，可能会切分为“Hel，lo，Wor，ld”四个子词对。而对于以字为基本单位的中文，一般会按照字节编码进行切分。例如，在 UTF-8 编码中，“我”会被编码为“E68891”，那么在 BPE 中可能就会切分成“E68”，“891”两个字词对\nBPE编码的词典越大,编码效果越好,在Embedding将其映射到隐藏空间的时候,也会带来模型参数的增加\n它的成功证明了更大的预训练数据和更大的预训练补偿的重要意义,这也是LLM诞生的基础之一\n3.1.3 ALBERT 优化角度:减小模型参数同时保持模型能力 改进角度:对模型结构进行优化并且对NSP预训练任务进行了改进-\u0026gt;SOP\n(1)优化一:将Embedding参数进行分解 一个方面:参数太大会造成极大的模型计算开销 零一个方面:从Word2Vec的成功来看,此项两并不需要很大的维度 优化方式: 在Embedding层的输出维度和隐藏层解绑,在中间加入一个线性矩阵进行维度变换 参数变化:Embedding的参数从V×H变化到V×E + E×H\n(2)优化二:跨层进行参数共享 通过共享参数降低模型的参数,同时可以极大程度地扩大隐藏层维度 实际上:ALNERT仅初始化一个Encoder层,24次运算均经过这一个Encoder层,可以实现一个更宽但是参数量更小地模型\n缺陷:训练效率仅略优于BERT,且推理速度还要更慢\n(3)优化三: 提出了SOP预训练任务 认为NSP过于简单,于是改进为SOP预训练 SOP任务的改进:\n正例:由两个连续句子组成 负例:将这两个句子的顺序反过来 迫使学习句子的拟合关系和顺序关系\neg:\n1 2 3 4 5 6 7 8 9 10 11 输入： Sentence A：I love you. Sentence B: Because you are wonderful. 输出： 1（正样本） 输入： Sentence A：Because you are wonderful. Sentence B: I love you. 输出： 0（负样本） 很多模型都是在BERT的架构上进行优化\n3.2 Encoder-Decoder PLM 3.2.1 T5 T5(Text-To-Text Transfer Transformer) 基本架构: T5 基于 Transformer 架构，包含编码器和解码器两个部分，使用自注意力机制和多头注意力捕捉全局依赖关系，利用相对位置编码处理长序列中的位置信息，并在每层中包含前馈神经网络进一步处理特征 T5 的大一统思想将不同的 NLP 任务如文本分类、问答、翻译等统一表示为输入文本到输出文本的转换 优点: 这种方法简化了模型设计、参数共享和训练过程，提高了模型的泛化能力和效率。通过这种统一处理方式，T5不仅减少了任务特定的模型调试工作，还能够使用相同的数据处理和训练框架，极大地提升了多任务学习的性能和应用的便捷性\n(1)模型结构: Encoder-Decoder 编码器用于处理输入文本,解码器用于生成输出文本,两者之间通过注意力机制进行信息交互 主要结构如图: 图片描述\n从整体来看 T5 的模型结构包括 Tokenizer 部分和 Transformer 部分。Tokenizer 部分主要负责将输入文本转换为模型可接受的输入格式，包括分词、编码等操作。Transformer 部分又分为 EncoderLayers 和 DecoderLayers 两部分，他们分别由一个个小的 Block组成，每个 Block 包含了多头注意力机制、前馈神经网络和 Norm 层。\nT5 模型的 Encoder 和 Decoder 部分都是基于 Transformer 架构设计的 Encoder-Decoder Attention 仅仅在位置编码和 Mask 机制上有所不同，主要是为了区分输入和输出序列。\n于原始Transformer不同的,T5的LayerNorm采用了RMSNorm,计算每一个神经元的均方根来归一化每个隐藏层的激活值 优点:更简单,只有一个可学参数,可以更好地适应不同地任务和数据集 RMS函数可以用下面地数学公式表示: $$ $$ \\mathrm{RMSNorm}(x) = \\frac{x}{\\sqrt{\\frac{1}{n}\\sum_{i=1}^{n}x_i^2 + \\epsilon}} \\cdot \\gamma $$ $$ 其中：\nxi 是输入向量的第 i 个元素 γ* 是可学习的缩放参数 n*n 是输入向量的维度数量 ϵ* 是一个小常数，用于数值稳定性（以避免除以零的情况） (2)预训练任务 训练所使用的数据集是一个大规模的文本数据集，包含了各种各样的文本数据，如维基百科、新闻、书籍等等。对数据经过细致的处理后，生成了用于训练的750GB 的数据集 C4，且已在 TensorflowData 中开源\nT5的预训练过程如下: 预训练任务:MLM,也成为BERT-style目标:在输入文本中随机遮蔽15%的token 输入格式:将将输入文本替换为\u0026quot;从文本到文本\u0026quot;格式 对于一个给定的文本序列:随机选择一些token进行遮蔽,用特殊占位符替换,被遮蔽的token序列作为输出目标 预训练数据集:C4:提取了大量干净的英语文本,经过了一定程度的清洗,去除了无意义的文本,重复的文本 多任务预训练:尝试将多个任务混合在一起进行预训练 预训练到微调的转换:微调在特定的数据集上进行训练并且根据任务调整编码策略\n(3) 大一统思想 所有的NLP任务都可以统一为文本到文本的任务 其设计理念:将不同类型的NLP任务都转化统一的格式:输入和输出都是文本\n对于不同的NLP任务，每次输入前都会加上一个任务描述前缀，明确指定当前任务的类型。这不仅帮助模型在预训练阶段学习到不同任务之间的通用特征，也便于在微调阶段迅速适应具体任务\n3.3 Decoder-only PLM 目前LLM的基础框架(除了RWKV,Mamba等非Transformer框架除外) 而目前作为开源 LLM 基本架构的 LLaMA 模型，也正是在 GPT 的模型架构基础上优化发展而来\n3.3.1 GPT(Generative Pre-Training Language Model) GPT的传奇故事:上市了的O/还是那个O/吗? GPT，即 Generative Pre-Training Language Model，是由 OpenAI 团队于 2018年发布的预训练语言模型。虽然学界普遍认可 BERT 作为预训练语言模型时代的代表，但首先明确提出预训练-微调思想的模型其实是 GPT。GPT 提出了通用预训练的概念，也就是在海量无监督语料上预训练，进而在每个特定任务上进行微调，从而实现这些任务的巨大收益。虽然在发布之初，由于性能略输于不久后发布的 BERT，没能取得轰动性成果，也没能让 GPT 所使用的 Decoder-Only 架构成为学界研究的主流，但 OpenAI 团队坚定地选择了不断扩大预训练数据、增加模型参数，在 GPT 架构上不断优化，最终在 2020年发布的 GPT-3 成就了 LLM 时代的基础，并以 GPT-3 为基座模型的 ChatGPT 成功打开新时代的大门，成为 LLM 时代的最强竞争者也是目前的最大赢家。\n(1)模型架构\u0026ndash;Decoder Only 如图:\nalt text\n选择Decoder进行模型框架的堆叠,也天生更适用于文本生成任务 更加契合NLG任务和Seq2Seq任务 过程: 自然语言输入-\u0026gt;tokenizer-\u0026gt;inputs_ids-\u0026gt;\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 自然语言输入 --\u0026gt; tokenizer input_ids --\u0026gt; Embedding 层 --\u0026gt; Positional Embedding（Sinusoidal 绝对位置编码） hidden_states --\u0026gt; Decoder 层 × 12 --\u0026gt; LayerNorm --\u0026gt; 带掩码的自注意力层（Masked Self-Attention） --\u0026gt; 通过参数矩阵生成 Query、Key、Value --\u0026gt; 注意力权重计算 --\u0026gt; 掩码矩阵遮蔽未来 token --\u0026gt; 仅允许关注当前及之前 token --\u0026gt; 残差连接 --\u0026gt; LayerNorm --\u0026gt; MLP（两个一维卷积核） --\u0026gt; 残差连接 --\u0026gt; 线性映射到词表维度 目标 token 序列 (2)预训练任务\u0026ndash;CLM(因果语言模型) Decoder-Only的结构更适合文本生成任务,所以其预训练任务也是最传统也最直接的预训练任务\u0026ndash;因果语言模型,Causal Language Model，下简称 CLM CLM 可以看作 N-gram 语言模型的一个直接扩展。N-gram 语言模型是基于前 N 个 token 来预测下一个 token，CLM 则是基于一个自然语言序列的前面所有 token 来预测下一个 token，通过不断重复该过程来实现目标文本序列的生成。也就是说，CLM 是一个经典的补全形式\n1 2 3 4 5 input: 今天天气 output: 今天天气很 input: 今天天气很 output：今天天气很好 BERT 之所以可以采用预训练+微调的范式取得重大突破，正是因为其选择的 MLM、NSP 可以在海量无监督语料上直接训练——而很明显，CLM 是更直接的预训练任务，其天生和人类书写自然语言文本的习惯相契合，也和下游任务直接匹配，相对于 MLM 任务更加直接，可以在任何自然语言文本上直接应用。因此，CLM 也可以使用海量的自然语言语料进行大规模的预训练\n(3)GPT系列模型的发展 自 GPT-1 推出开始，OpenAI 一直坚信 Decoder-Only 的模型结构和“体量即正义”的优化思路，不断扩大预训练数据集、模型体量并对模型做出一些小的优化和修正，来不断探索更强大的预训练模型。从被 BERT 压制的 GPT-1，到没有引起足够关注的 GPT-2，再到激发了涌现能力、带来大模型时代的 GPT-3，最后带来了跨时代的 ChatGPT，OpenAI 通过数十年的努力证明了其思路的正确性。\n下表总结了从 GPT-1 到 GPT-3 的模型结构、预训练语料大小的变化：\n模型 Decoder Layer Hidden_size 注意力头数 注意力维度 总参数量 预训练语料 GPT-1 12 3072 12 768 0.12B 5GB GPT-2 48 6400 25 1600 1.5B 40GB GPT-3 96 49152 96 12288 175B 570GB GPT-1 是 GPT 系列的开山之作，也是第一个使用 Decoder-Only 的预训练模型。但是，GPT-1 的模型体量和预训练数据都较少，沿承了传统 Transformer 的模型结构，使用了 12层 Decoder Block 和 768 的隐藏层维度，模型参数量仅有 1.17亿（0.12B），在大小为 5GB 的 BooksCorpus 数据集上预训练得到。可以看到，GPT-1 的参数规模与预训练规模和 BERT-base 是大致相当的，但其表现相较于 BERT-base 却有所不如，这也是 GPT 系列模型没能成为预训练语言模型时代的代表的原因。\nGPT-2 则是 OpenAI 在 GPT-1 的基础上进一步探究预训练语言模型多任务学习能力的产物。GPT-2 的模型结构和 GPT-1 大致相当，只是扩大了模型参数规模、将 Post-Norm 改为了 Pre-Norm（也就是先进行 LayerNorm 计算，再进入注意力层计算）。这些改动的核心原因在于，由于模型层数增加、体量增大，梯度消失和爆炸的风险也不断增加，为了使模型梯度更稳定对上述结构进行了优化。\nGPT-2 的核心改进是大幅增加了预训练数据集和模型体量。GPT-2 的 Decoder Block 层数达到了48（注意，GPT-2 共发布了四种规格的模型，此处我们仅指规格最大的 GPT-2 模型），隐藏层维度达到了 1600，模型整体参数量达 15亿（1.5B），使用了自己抓取的 40GB 大小的 WebText 数据集进行预训练，不管是模型结构还是预训练大小都超过了 1代一个数量级。\nGPT-2 的另一个重大突破是以 zero-shot（零样本学习）为主要目标，也就是不对模型进行微调，直接要求模型解决任务。例如，在传统的预训练-微调范式中，我们要解决一个问题，一般需要收集几百上千的训练样本，在这些训练样本上微调预训练语言模型来实现该问题的解决。而 zero-shot 则强调不使用任何训练样本，直接通过向预训练语言模型描述问题来去解决该问题。zero-shot 的思路自然是比预训练-微调范式更进一步、更高效的自然语言范式，但是在 GPT-2 的时代，模型能力还不足够支撑较好的 zero-shot 效果，在大模型时代，zero-shot 及其延伸出的 few-shot（少样本学习）才开始逐渐成为主流。\nGPT-3 则是更进一步展示了 OpenAI“力大砖飞”的核心思路，也是 LLM 的开创之作。在 GPT-2 的基础上，OpenAI 进一步增大了模型体量和预训练数据量，整体参数量达 175B，是当之无愧的“大型语言模型”。在模型结构上，基本没有大的改进，只是由于巨大的模型体量使用了稀疏注意力机制来取代传统的注意力机制。在预训练数据上，则是分别从 CC、WebText、维基百科等大型语料集中采样，共采样了 45T、清洗后 570GB 的数据。根据推算，GPT-3 需要在 1024张 A100（80GB 显存）的分布式训练集群上训练 1个月。\n之所以说 GPT-3 是 LLM 的开创之作，除去其巨大的体量带来了涌现能力的凸显外，还在于其提出了 few-shot 的重要思想。few-shot 是在 zero-shot 上的改进，研究者发现即使是 175B 大小的 GPT-3，想要在 zero-shot 上取得较好的表现仍然是一件较为困难的事情。而 few-shot 是对 zero-shot 的一个折中，旨在提供给模型少样的示例来教会它完成任务。few-shot 一般会在 prompt（也就是模型的输入）中增加 3~5个示例，来帮助模型理解。例如，对于情感分类任务：\n1 2 3 zero-shot：请你判断‘这真是一个绝佳的机会’的情感是正向还是负向，如果是正向，输出1；否则输出0 few-shot：请你判断‘这真是一个绝佳的机会’的情感是正向还是负向，如果是正向，输出1；否则输出0。你可以参考以下示例来判断：‘你的表现非常好’——1；‘太糟糕了’——0；‘真是一个好主意’——1。Copy to clipboardErrorCopied 通过给模型提供少量示例，模型可以取得远好于 zero-shot 的良好表现。few-shot 也被称为上下文学习（In-context Learning），即让模型从提供的上下文中的示例里学习问题的解决方法。GPT-3 在 few-shot 上展现的强大能力，为 NLP 的突破带来了重要进展。如果对于绝大部分任务都可以通过人为构造 3~5个示例就能让模型解决，其效率将远高于传统的预训练-微调范式，意味着 NLP 的进一步落地应用成为可能——而这，也正是 LLM 的核心优势。\n在 GPT 系列模型的基础上，通过引入预训练-指令微调-人类反馈强化学习的三阶段训练，OpenAI 发布了跨时代的 ChatGPT，引发了大模型的热潮。也正是在 GPT-3 及 ChatGPT 的基础上，LLaMA、ChatGLM 等模型的发布进一步揭示了 LLM 的无尽潜力。在下一节，我们将深入剖析目前 LLM 的普适架构——LLaMA。\n总结要点:参数量变大;zero-shot;few-shot\n3.3.2 LLaMA 由Meta(前Facebook)开发\n(1)模型架构\u0026ndash;Decoder Only alt text整体架构和GPT类似,只是在模型规模和预训练数据集有区别 位置编码部分也有区别:方式和时机 LLaMA:旋转编码(相对位置),在注意力机制中 GPT:正弦函数,余弦函数(绝对位置);在Embedding之后\n与GPT类似，LLaMA模型的处理流程也始于将输入文本通过tokenizer进行编码，转化为一系列的input_ids。这些input_ids是模型能够理解和处理的数据格式。接下来，这些input_ids会经过embedding层的转换，这里每个input_id会被映射到一个高维空间中的向量，即词向量。同时，输入文本的位置信息也会通过positional embedding层被编码，以确保模型能够理解词序上下文信息。\n这样，input_ids经过embedding层和positional embedding层的结合，形成了hidden_states。hidden_states包含了输入文本的语义和位置信息，是模型进行后续处理的基础，hidden_states随后被输入到模型的decoder层。\n在decoder层中，hidden_states会经历一系列的处理，这些处理由多个decoder block组成。每个decoder block都是模型的核心组成部分，它们负责对hidden_states进行深入的分析和转换。在每个decoder block内部，首先是一个masked self-attention层。在这个层中，模型会分别计算query、key和value这三个向量。这些向量是通过hidden_states线性变换得到的，它们是计算注意力权重的基础。然后使用softmax函数计算attention score，这个分数反映了不同位置之间的关联强度。通过attention score，模型能够确定在生成当前词时，应该给予不同位置的hidden_states多大的关注。然后，模型将value向量与attention score相乘，得到加权后的value，这就是attention的结果。\n在完成masked self-attention层之后，hidden_states会进入MLP层。在这个多层感知机层中，模型通过两个全连接层对hidden_states进行进一步的特征提取。第一个全连接层将hidden_states映射到一个中间维度，然后通过激活函数进行非线性变换，增加模型的非线性能力。第二个全连接层则将特征再次映射回原始的hidden_states维度。\n最后，经过多个decoder block的处理，hidden_states会通过一个线性层进行最终的映射，这个线性层的输出维度与词表维度相同。这样，模型就可以根据hidden_states生成目标序列的概率分布，进而通过采样或贪婪解码等方法，生成最终的输出序列。这一过程体现了LLaMA模型强大的序列生成能力。\n(2) 发展历程 LLaMA-1 系列：\nMeta于2023年2月发布了LLaMA-1，包括7B、13B、30B和65B四个参数量版本。 这些模型在超过1T token的语料上进行了预训练，其中最大的65B参数模型在2,048张A100 80G GPU上训练了近21天。 LLaMA-1因其开源性和优异性能迅速成为开源社区中最受欢迎的大模型之一。 LLaMA-2 系列：\n2023年7月，Meta发布了LLaMA-2，包含7B、13B、34B和70B四个参数量版本，除了34B模型外，其他均已开源。 LLaMA-2将预训练的语料扩充到了2T token，并将模型的上下文长度从2,048翻倍到了4,096。 引入了分组查询注意力机制（Grouped-Query Attention, GQA）等技术。 LLaMA-3 系列：\n2024年4月，Meta发布了LLaMA-3，包括8B和70B两个参数量版本，同时透露400B的LLaMA-3还在训练中。 LLaMA-3支持8K长文本，并采用了编码效率更高的tokenizer，词表大小为128K。 使用了超过15T token的预训练语料，是LLaMA-2的7倍多。 LLaMA模型以其技术创新、多参数版本、大规模预训练和高效架构设计而著称。模型支持从70亿到数百亿不等的参数量，适应不同规模的应用需求。LLaMA-1以其开源性和优异性能迅速受到社区欢迎，而LLaMA-2和LLaMA-3进一步通过引入分组查询注意力机制和支持更长文本输入，显著提升了模型性能和应用范围。特别是LLaMA-3，通过采用128K词表大小的高效tokenizer和15T token的庞大训练数据，实现了在多语言和多任务处理上的重大进步。Meta对模型安全性和社区支持的持续关注，预示着LLaMA将继续作为AI技\n3.3.3 GLM (1)模型框架-相较于GPT的略微修正 GLM 最初是由清华计算机系推出的一种通用语言模型基座，其核心思路是在传统 CLM 预训练任务基础上，加入 MLM 思想，从而构建一个在 NLG 和 NLU 任务上都具有良好表现的统一模型。\n使用 Post Norm 而非 Pre Norm。Post Norm 是指在进行残差连接计算时，先完成残差计算，再进行 LayerNorm 计算；而类似于 GPT、LLaMA 等模型都使用了 Pre Norm，也就是先进行 LayerNorm 计算，再进行残差的计算。相对而言，Post Norm 由于在残差之后做归一化，对参数正则化的效果更强，进而模型的鲁棒性也会更好；Pre Norm相对于因为有一部分参数直接加在了后面，不需要对这部分参数进行正则化，正好可以防止模型的梯度爆炸或者梯度消失。因此，对于更大体量的模型来说，一般认为 Pre Norm 效果会更好。但 GLM 论文提出，使用 Post Norm 可以避免 LLM 的数值错误（虽然主流 LLM 仍然使用了 Pre Norm）； 使用单个线性层实现最终 token 的预测，而不是使用 MLP；这样的结构更加简单也更加鲁棒，即减少了最终输出的参数量，将更大的参数量放在了模型本身； 激活函数从 ReLU 换成了 GeLUs。ReLU 是传统的激活函数，其核心计算逻辑为去除小于 0的传播，保留大于 0的传播；GeLUs 核心是对接近于 0的正向传播，做了一个非线性映射，保证了激活函数后的非线性输出，具有一定的连续性。 (2)预训练任务-GLM(General Language Model，通用语言模型) GLM 是一种结合了自编码思想和自回归思想的预训练方法: 所谓自编码思想，其实也就是 MLM 的任务学习思路，在输入文本中随机删除连续的 tokens，要求模型学习被删除的 tokens；所谓自回归思想，其实就是传统的 CLM 任务学习思路，也就是要求模型按顺序重建连续 tokens。\nGLM 通过优化一个自回归空白填充任务来实现 MLM 与 CLM 思想的结合。其核心思想是，对于一个输入序列，会类似于 MLM 一样进行随机的掩码，但遮蔽的不是和 MLM 一样的单个 token，而是每次遮蔽一连串 token；模型在学习时，既需要使用遮蔽部分的上下文预测遮蔽部分，在遮蔽部分内部又需要以 CLM 的方式完成被遮蔽的 tokens 的预测。例如，输入和输出可能是：\n1 2 输入：I \u0026lt;MASK\u0026gt; because you \u0026lt;MASK\u0026gt; 输出：\u0026lt;MASK\u0026gt; - love you; \u0026lt;MASK\u0026gt; - are a wonderful person 不过，GLM 预训练任务更多的优势还是展现在预训练模型时代，迈入 LLM 时代后，针对于超大规模、体量的预训练，CLM 展现出远超 MLM 的优势。通过将模型体量加大、预训练规模扩大，CLM 预训练得到的生成模型在文本理解上也能具有超出 MLM 训练的理解模型的能力，因此，ChatGLM 系列模型也仅在第一代模型使用了 GLM 的预训练思想，从 ChatGLM2 开始，还是回归了传统的 CLM 建模。虽然从 LLM 的整体发展路径来看，GLM 预训练任务似乎是一个失败的尝试，但通过精巧的设计将 CLM 与 MLM 融合，并第一时间产出了中文开源的原生 LLM，其思路仍然存在较大的借鉴意义。\n(3)GLM家族的发展: 在 GLM 模型（即使用原生 GLM 架构及预训练任务的早期预训练模型）的基础上，参考 ChatGPT 的技术思路进行 SFT 和 RLHF，智谱于 23年 3月发布了第一个中文开源 LLM ChatGLM-6B，成为了众多中文 LLM 研究者的起点。ChatGLM-6B 在 1T 语料上进行预训练，支持 2K 的上下文长度。\n在 23年 6月，智谱就开源了 ChatGLM2-6B。相对于一代，ChatGLM2 将上下文长度扩展到了 32K，通过更大的预训练规模实现了模型性能的大幅度突破。不过，在 ChatGLM2 中，模型架构就基本回归了 LLaMA 架构，引入 MQA 的注意力机制，预训练任务也回归经典的 CLM，放弃了 GLM 的失败尝试。\nChatGLM3-6B 发布于 23年 10月，相对于二代在语义、数学、推理、代码和知识方面都达到了当时的 SOTA 性能，但是官方给出的技术报告说明 ChatGLM3 在模型架构上相对二代没有变化，最主要的优化来源是更多样化的训练数据集、更充足的训练步骤和更优化的训练策略。ChatGLM3 的另一个重要改进在于其开始支持函数调用与代码解释器，开发者可以直接使用开源的 ChatGLM3 来实现 Agent 开发，具有更广泛的应用价值。\n2024年 1月，智谱发布了支持 128K 上下文，包括多种类型的 GLM-4 系列模型，评估其在英文基准上达到了 GPT-4 的水平。不过，智谱并未直接开源 GLM-4，而是开源了其轻量级版本 GLM-4-9B 模型，其在 1T token 的多语言语料库上进行预训练，上下文长度为 8K，并使用与 GLM-4 相同的管道和数据进行后训练。在训练计算量较少的情况下，其超越了 Llama-3-8B，并支持 GLM-4 中所有工具的功能。\n","permalink":"https://tuanzi-wow.cn/llm/llm-pretrain-plm/","summary":"\u003ch1 id=\"第三章-预训练语言模型plm\"\u003e第三章 预训练语言模型(PLM)\u003c/h1\u003e\n\u003ch2 id=\"31--encoder-only-plm\"\u003e3.1  Encoder-only PLM\u003c/h2\u003e\n\u003ch3 id=\"311-bert-bidirectional-encoder-representations-from-transformers\"\u003e3.1.1 BERT( Bidirectional Encoder Representations from Transformers)\u003c/h3\u003e\n\u003cp\u003e由Google发布的,引领了预训练+微调的的潮流\u003c/p\u003e\n\u003ch4 id=\"1思想沿承\"\u003e(1)思想沿承\u003c/h4\u003e\n\u003cp\u003e统一了多种思想的预训练框架,包括:\n\u003cstrong\u003eTransformer架构:\u003c/strong\u003e\n通过在Transformer的模型基座上进行优化,将Encoder的结构进行堆叠,扩大模型参数\n\u003cstrong\u003e预训练+微调范式:\u003c/strong\u003e\nELMo模型基于LSTM双向架构\n而BERT则将模型框架调整为了Transformer,引入了更适合文本理解,能捕捉深层双向语义的预训练任务MLM\u003c/p\u003e","title":"LLM · 预训练语言模型（PLM）"},{"content":"提示词要素 如果您接触过大量提示工程相关的示例和应用，您会注意到提示词是由一些要素组成的。\n提示词可以包含以下任意要素： 指令：想要模型执行的特定任务或指令。 上下文：包含外部信息或额外的上下文信息，引导语言模型更好地响应。 输入数据：用户输入的内容或问题。 输出指示：指定输出的类型或格式。 为了更好地演示提示词要素，下面是一个简单的提示，旨在完成文本分类任务：\n提示词\n1 请将文本分为中性、否定或肯定文本：我觉得食物还可以。情绪： 在上面的提示示例中，指令是“将文本分类为中性、否定或肯定”。输入数据是“我认为食物还可以”部分，使用的输出指示是“情绪：”。请注意，此基本示例不使用上下文，但也可以作为提示的一部分提供。例如，此文本分类提示的上下文可以是作为提示的一部分提供的其他示例，以帮助模型更好地理解任务并引导预期的输出类型。\n注意，提示词所需的格式取决于您想要语言模型完成的任务类型，并非所有以上要素都是必须的。我们会在后续的指南中提供更多更具体的示例。\n","permalink":"https://tuanzi-wow.cn/promptengineering/prompt-elements/","summary":"\u003ch1 id=\"提示词要素\"\u003e提示词要素\u003c/h1\u003e\n\u003cp\u003e如果您接触过大量提示工程相关的示例和应用，您会注意到提示词是由一些要素组成的。\u003c/p\u003e\n\u003ch2 id=\"提示词可以包含以下任意要素\"\u003e提示词可以包含以下任意要素：\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e指令\u003c/strong\u003e：想要模型执行的特定任务或指令。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e上下文\u003c/strong\u003e：包含外部信息或额外的上下文信息，引导语言模型更好地响应。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e输入数据\u003c/strong\u003e：用户输入的内容或问题。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e输出指示\u003c/strong\u003e：指定输出的类型或格式。\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e为了更好地演示提示词要素，下面是一个简单的提示，旨在完成文本分类任务：\u003c/p\u003e","title":"Prompt Engineering · 提示词要素"},{"content":"Spring AI Tool Calling使用指南之我不用JAVA好多年 概述: Tool Calling(Function Calling)允许模型与一组API或者工具进行交互,增强模型的能力\n主要功能:\n信息检索:从外部数据源获取信息,比如:数据库,Web服务,例如:Web_Search 执行操作:在软件系统执行特定操作:例如:预定航班,生成代码 Spring AI Alibaba拓展服务:\n搜索引擎 翻译服务 地图服务 数据服务 开发工具 其他工具 详细使用说明 Python Tool示例(你个臭执行Python代码的) PythonTool 使用 GraalVM polyglot 在沙箱环境中执行 Python 代码。这个工具允许 AI 代理执行 Python 代码片段并获取结果。\n依赖配置: 使用Maven添加依赖:\n1 2 3 4 5 \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;com.alibaba.cloud.ai\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-ai-alibaba-starter-tool-calling-python\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;${version}\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; 注意：Python Tool 需要 GraalVM polyglot 依赖，这些依赖已作为可选依赖包含。如果需要使用，请确保 GraalVM polyglot 在 classpath 中。\n自动配置 Python Tool默认启用,会自动注册为ToolCallback并可供AI代理使用,如果需要禁用,可以在配置文件中设置\n1 2 3 4 5 6 spring: ai: alibaba: python: tool: enabled: false 基本使用 示例1:在ChatClient中使用 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 import com.alibaba.cloud.ai.agent.python.tool.PythonTool; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.model.ChatModel; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class PythonToolService { @Autowired private ChatModel chatModel; @Autowired private PythonTool pythonTool; public String executeCalculation(String question) { // 创建 ChatClient 并添加 Python Tool ChatClient chatClient = ChatClient.builder(chatModel) .tools(pythonTool.createPythonToolCallback(PythonTool.DESCRIPTION)) .build(); // 使用工具进行对话 String response = chatClient.prompt(question) .call() .content(); return response; } } 示例2:直接使用PythonTool 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import com.alibaba.cloud.ai.agent.python.tool.PythonTool; import org.springframework.ai.chat.model.ToolContext; public class DirectPythonToolUsage { public void executePythonCode() { PythonTool pythonTool = new PythonTool(); // 创建请求 PythonTool.PythonRequest request = new PythonTool.PythonRequest(\u0026#34;2 + 2\u0026#34;); // 执行 Python 代码 String result = pythonTool.apply(request, new ToolContext()); System.out.println(\u0026#34;Result: \u0026#34; + result); // 输出: Result: 4 } } 示例3:执行复杂计算 还是通过在client中调用PythonTool\n示例4:字符串操作 类似于示例2:创建request,调用apply(request,toolcontext)\n1 2 3 4 PythonTool.PythonRequest request1 = new PythonTool.PythonRequest( \u0026#34;\u0026#39;Hello, \u0026#39; + \u0026#39;World\u0026#39;\u0026#34; ); String result1 = pythonTool.apply(request1, toolContext); 示例5:在Spring Boot应用中使用 类似于示例1 创建client并将PythonTool写入,只是这里的1应用场景是Spring Boot\n示例6:自定义ToolCallback (来你告诉我这不是在Spring Boot里面运行吗?)\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import com.alibaba.cloud.ai.agent.python.tool.PythonTool; import org.springframework.ai.tool.ToolCallback; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class PythonToolConfiguration { @Bean public PythonTool pythonTool() { return new PythonTool(); } @Bean public ToolCallback pythonToolCallback(PythonTool pythonTool) { return PythonTool.createPythonToolCallback( \u0026#34;执行 Python 代码并返回结果。支持数学计算、字符串处理、列表操作等。\u0026#34; ); } } 示例7:多工具组合使用 PythonTool和其他Tool一起调用,看看就好,也是创建client并添加tools,这次是多个\n安全特性: PythonTool在沙箱环境中运行,具有以下的安全限制\n文件I/O已禁用:无法读写文件 本地访问已禁用:无法访问本地系统资源 进程创建已禁用:无法创建新的进程 默认限制所有访问:所有访问受限 保证了Python代码执行的安全性\n支持的数据类型 字符串:直接返回 数字:转化为字符串返回 布尔值:转化为字符串返回 数组/列表:转化为字符串表现形式 其他类型:使用toString()方法转换 注意事项 GraalVM 依赖：确保 GraalVM polyglot 依赖在 classpath 中 性能考虑：每次执行都会创建新的 Context，对于频繁调用可能需要优化 错误处理：代码执行错误会被捕获并返回错误消息 代码限制：由于安全限制，某些 Python 功能可能不可用 资源管理：Context 会自动关闭，无需手动管理 一个完整的示例:AI代理使用Python Tool: eee,这算哪门子示例,好吧也算\n1 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 import com.alibaba.cloud.ai.agent.python.tool.PythonTool; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.model.ChatModel; import org.springframework.stereotype.Service; @Service public class PythonToolAgentService { private final ChatClient chatClient; public PythonToolAgentService(ChatModel chatModel, PythonTool pythonTool) { this.chatClient = ChatClient.builder(chatModel) .tools(pythonTool.createPythonToolCallback(PythonTool.DESCRIPTION)) .build(); } public String solveMathProblem(String problem) { String prompt = String.format(\u0026#34;\u0026#34;\u0026#34; 请使用 Python 工具解决以下数学问题： %s 请展示计算过程。 \u0026#34;\u0026#34;\u0026#34;, problem); return chatClient.prompt(prompt) .call() .content(); } public String analyzeData(String dataDescription) { String prompt = String.format(\u0026#34;\u0026#34;\u0026#34; 请使用 Python 工具分析以下数据： %s 请计算基本统计信息（平均值、中位数、标准差等）。 \u0026#34;\u0026#34;\u0026#34;, dataDescription); return chatClient.prompt(prompt) .call() .content(); } } 支持的拓展实现: https://java2ai.com/integration/toolcalls/tool-calls#%E7%9B%AE%E5%BD%95 自己去这里看\n使用说明 所有的Tool Calling实现都遵循相同的使用模式\n添加依赖:在pom.xml或者build.gradle中添加相应的依赖 自动配置:Spring Boot的生态真实太好用了,你们知道吗?会自动注册为ToolCallback 在Chatclient中使用:通过ChatClient.builder().tools()添加工具 AI模型调用:AI模型会根据会话内容自动决定是否调用工具 通用接口 你这么好用你不早说?\n所有 Tool 都实现了 BiFunction\u0026lt;Request, ToolContext, String\u0026gt; 接口，并通过 FunctionToolCallback 包装为 ToolCallback：\n1 2 3 4 5 public interface ToolCallback { String getName(); String getDescription(); ToolResponse call(ToolRequest request); } 配置说明 大多数工具都支持通过Spring Boot配置属性进行配置(又是你宝宝)\n1 2 3 4 5 6 spring: ai: alibaba: tool-name: enabled: true # 启用/禁用工具 # 其他工具特定配置 认证和API key 参考各个工具的文档\n最佳实践 工具选择：根据实际需求选择合适的工具，避免添加不必要的工具 错误处理：工具调用可能失败，确保有适当的错误处理机制 性能优化：对于频繁调用的工具，考虑缓存结果 安全性：确保 API Key 等敏感信息的安全存储 成本控制：某些工具（如搜索、翻译）可能产生费用，注意使用量 ","permalink":"https://tuanzi-wow.cn/agent/spring-ai-tool-calling/","summary":"\u003ch1 id=\"spring-ai-tool-calling使用指南之我不用java好多年\"\u003eSpring AI Tool Calling使用指南之我不用JAVA好多年\u003c/h1\u003e\n\u003ch2 id=\"概述\"\u003e概述:\u003c/h2\u003e\n\u003cp\u003eTool Calling(Function Calling)允许模型与一组API或者工具进行交互,增强模型的能力\u003c/p\u003e\n\u003cp\u003e主要功能:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e信息检索:从外部数据源获取信息,比如:数据库,Web服务,例如:Web_Search\u003c/li\u003e\n\u003cli\u003e执行操作:在软件系统执行特定操作:例如:预定航班,生成代码\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eSpring AI Alibaba拓展服务:\u003c/p\u003e","title":"Spring AI Tool Calling 使用指南"},{"content":"Prompting overview Larger task,plz include these parts: Goal Context Output: format,length,level of detail Boundaries\nDescribe the result u need: not detailed list,but needed details those change what AI should produce\nAdd useful context: for example: necessary attach file; visual work: screenshot,image;and point out which area matters web search: Explicitly( 显式地 ) ask to use Web Search in the conversation. share resources: using a directory\nUse connected sources: name where it should look and what if should find\nUsing plugins: plugins provide reusable instructions and connections to tools Ask for the result u need and let the active surface choose from the tools available to it\nPersonalize : Put preferences that should apply across chats in Settings \u0026gt; Personalization as custom instructions.\nSet boundaries that prevent real problems: avoid creating extra work or taking an action u did not asked.\nKeep the approved dates and budget figures unchanged. Use only the supplied sources. Flag missing information instead of guessing. Keep recommendations within the stated budget. Prepare the message as a draft. Don\u0026rsquo;t send it. focus on the one or two boundaries that really matters Improve the result with follow-up messages: Your first prompt doesn\u0026rsquo;t need to be perfect. Review the result, then ask for the specific change you want.\nSteering and queuing: Steer add to the current run;Queue for the next run. In the CLI,using Enter to steer,and Tab to queue.\nPut the pieces together: include the four parts(maybe not all) above\nUse voice dictation: Ctrl+Shift+Tab\nPrompting examples for chat: start with the outcome u want and add details when it changes to the answer\nUnderstand a topic 1 2 Explain how compound interest works for someone who has never invested. Use one concrete example and define any financial terms you introduce. Draft and refine writing 1 2 Draft a friendly email declining this invitation because I will be traveling. Keep it under 120 words and leave the door open for a future event. Compare options 1 2 3 Compare these two phone plans for one person who travels internationally twice a year. Show the important differences in a table, then recommend one and explain the tradeoff. Make a practical plan 1 2 Plan five weekday dinners that take less than 30 minutes. Avoid peanuts, reuse ingredients across meals, and finish with one consolidated shopping list. Prompting codex: Name the behavior u want,points to the relevant code or codebase or reproduction steps,preserves important constraints and says how to verify the change.\nFor a muti-step task,/plan when u want codex to investigate and propose an approach before editing. /goal to set a persistent goal CLI: using /mention and @ path autocomplete Codex runs local commands inside a sandbox that limits file and network access\nExplain a codebase: 1 2 3 4 5 6 Explain how the request flows through the selected code. Include: - a short summary of the responsibilities of each module involved - what data is validated and where - one or two \u0026#34;gotchas\u0026#34; to watch for when changing this Fix a bug: Give Codex a reproduction recipe, plus the file(s) you suspect\nContext notes: by u and by Codex itself Verification: Run the repro steps after fix if u have a standard check pipeline ,ask it to run it\nWrite a test: Prompt with a function name: by the way,function name should be meaningful\n","permalink":"https://tuanzi-wow.cn/codex/codex-prompting/","summary":"\u003ch2 id=\"prompting\"\u003ePrompting\u003c/h2\u003e\n\u003ch2 id=\"overview\"\u003eoverview\u003c/h2\u003e\n\u003cp\u003eLarger task,plz include these parts:\nGoal\nContext\nOutput: format,length,level of detail\nBoundaries\u003c/p\u003e\n\u003ch2 id=\"describe-the-result-u-need\"\u003eDescribe the result u need:\u003c/h2\u003e\n\u003cp\u003enot detailed list,but needed details those change what AI should produce\u003c/p\u003e\n\u003ch2 id=\"add-useful-context\"\u003eAdd useful context:\u003c/h2\u003e\n\u003cp\u003efor example:\nnecessary attach file;\nvisual work: screenshot,image;and point out which area matters\nweb search: Explicitly( 显式地 ) ask to use Web Search in the conversation.\nshare resources: using a directory\u003c/p\u003e\n\u003ch2 id=\"use-connected-sources\"\u003eUse connected sources:\u003c/h2\u003e\n\u003cp\u003ename where it should look and what if should find\u003c/p\u003e","title":"Prompting 要点"},{"content":"Context editing Automatically manage conversation context as it grows with context editing 注意断句\nOverview The strategies on this page are useful for specific scenarios where you need more fine-grained control over what content is cleared.\n更适用于在特定场景中,你需要清楚哪些内容进行更加细粒度的控制\nContext editing allows you to selectively clear specific content from conversation history as it grows. Beyond optimizing costs and staying within limits, this is about actively curating what Claude sees: context is a finite resource with diminishing returns, and irrelevant content degrades model focus. Context editing gives you fine-grained runtime control over that curation. For the broader principles behind context management, see Effective context engineering.\n上下文编辑允许你选择性地清楚会话内容\n除了控制成本和避免超出限制以外,这也是在主动管理Claude对于上下文的可见性:上下文是一种有限的资源,且存在边界效应;无关内容会导致模型降级和降低注意力\n主要提供了三种方法:\nTool result clearing: 适用于工具调用频繁且旧的工具调用结果不再需要的场景 Thinking block clear:可有有选择性地保留最近地think block Client-side SDK compaction:An SDK-based alternative for summary-based context management (server-side compaction is generally preferred)官方Compaction的替代 Approach Where it runs Strategies How it works Server-side API Tool result clearing (clear_tool_uses_20250919) Thinking block clearing (clear_thinking_20251015) Applied before the prompt reaches Claude. Clears specific content from conversation history. Each strategy can be configured independently. Client-side SDK Compaction Available in TypeScript and Ruby SDKs when using tool_runner. Generates a summary and replaces full conversation history. See Client-side compaction. Server-side Strategies Tool result clearing when conversation context grows beyond your configured threshold Best for agentic workflows with heavy tool use and Older tool results are no longer needed once Claude has processed them.\n通过清楚oldest tool result并且使用占位文本进行替换来告知模型结果已经被移除 默认情况下,只有results会被移除,但是你也可以在配置文件中将clear_tool_call 甚至为True,这样会同时清楚tool_calls以及调用时的tool use parameters和tool_results\nThink block clearing The clear_thinking_20251015 strategy manages thinking blocks in conversations when extended thinking is enabled. This strategy gives you control over thinking preservation: you can choose to keep more thinking blocks to maintain reasoning continuity, or clear them more aggressively to save context space. 用于启用了extend_thinking的会话中的think_blocks; 你可以选择保留更多的thinking_block也可以积极清理来节省上下文空间\nDefault behavior: The default varies by model class.\nModel class Keep all prior thinking Keep only the last turn\u0026rsquo;s thinking Opus Claude Opus 4.5 and later Claude Opus 4.1 and earlier Sonnet Claude Sonnet 4.6 and later Claude Sonnet 4.5 and earlier Haiku (none) All models through Claude Haiku 4.5 Fable and Mythos All models (none) You can override the default,if your code runs across multiple models tiers,ser **keep **explicitly rather than relying on the per-model default\nWith interleaved thinking,there includes multiple thinking blocks\nContext editing happens server-side Context editing is applied server-side before the prompt reaches Claude.\n","permalink":"https://tuanzi-wow.cn/%E4%B8%8A%E4%B8%8B%E6%96%87%E5%B7%A5%E7%A8%8B/claude_context_management/context-editing/","summary":"\u003ch1 id=\"context-editing\"\u003eContext editing\u003c/h1\u003e\n\u003cp\u003eAutomatically manage conversation context as it grows with context editing\n注意断句\u003c/p\u003e\n\u003ch2 id=\"overview\"\u003eOverview\u003c/h2\u003e\n\u003cp\u003eThe strategies on this page are useful for specific scenarios where you need more fine-grained control over what content is cleared.\u003c/p\u003e\n\u003cp\u003e更适用于在特定场景中,你需要清楚哪些内容进行更加细粒度的控制\u003c/p\u003e\n\u003cp\u003eContext editing allows you to selectively clear specific content from conversation history as it grows. Beyond optimizing costs and staying within limits, this is about actively curating what Claude sees: context is a finite resource with diminishing returns, and irrelevant content degrades model focus. Context editing gives you fine-grained runtime control over that curation. For the broader principles behind context management, see \u003ca href=\"https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents\"\u003eEffective context engineering\u003c/a\u003e.\u003c/p\u003e","title":"Context Management · Context Editing"},{"content":"Function Call是什么? 实现了从只会说话到能做事情 生成文本-\u0026gt;Function_Call-\u0026gt;操作外部程序\nFunction Call的工作原理: 分为四步:定义函数-\u0026gt;选择函数(模型选择)-\u0026gt;执行函数-\u0026gt;返回结果(生成回答)\n定义函数: 用JSON格式描述:函数名称(name),功能说明(description),参数(params)\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 { \u0026#34;tools\u0026#34;: [ { \u0026#34;type\u0026#34;: \u0026#34;function\u0026#34;, \u0026#34;function\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;get_weather\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;获取指定城市的实时天气\u0026#34;, \u0026#34;parameters\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;object\u0026#34;, \u0026#34;properties\u0026#34;: { \u0026#34;city\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;string\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;城市名称，比如：上海\u0026#34; } }, \u0026#34;required\u0026#34;: [\u0026#34;city\u0026#34;] } } } ] } 模型判断: LLM分析意图并调用函数\n执行函数 LLM本身不执行函数,而是输出调用意图+函数参数,应用程序拿到指令后调用函数执行函数\n生成回答 LLM拿到结果并执行回答\n为什么Function Call这么重要 解决了两个问题:\nwhen:什么时候调用:LLM根据用户自然语言判断意图 传入什么参数:LLM从自然语言结构化传入参数 最底层的技术基础\n","permalink":"https://tuanzi-wow.cn/agent/xiaolin-function-calling/","summary":"\u003ch1 id=\"function-call是什么\"\u003eFunction Call是什么?\u003c/h1\u003e\n\u003ch2 id=\"实现了从只会说话到能做事情\"\u003e实现了从只会说话到能做事情\u003c/h2\u003e\n\u003cp\u003e生成文本-\u0026gt;Function_Call-\u0026gt;操作外部程序\u003c/p\u003e\n\u003ch2 id=\"function-call的工作原理\"\u003eFunction Call的工作原理:\u003c/h2\u003e\n\u003cp\u003e分为四步:定义函数-\u0026gt;选择函数(模型选择)-\u0026gt;执行函数-\u0026gt;返回结果(生成回答)\u003c/p\u003e","title":"Function Call 是什么"},{"content":"1.二叉树的中序遍历： 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 # Definition for a binary tree node. # class TreeNode: # def __init__(self, val=0, left=None, right=None): # self.val = val # self.left = left # self.right = right class Solution: global ans = [] def inorderTraversal(self, root: Optional[TreeNode]) -\u0026gt; List[int]: if not root: return [] self.inorderTraversal(root.left) ans.append(root.val) self.inorderTraversal(root.right) return ans 2.二叉树的最大深度： 递递的归\n1 2 3 4 5 6 7 8 9 10 11 12 13 # Definition for a binary tree node. # class TreeNode: # def __init__(self, val=0, left=None, right=None): # self.val = val # self.left = left # self.right = right class Solution: def maxDepth(self, root: Optional[TreeNode]) -\u0026gt; int: if root == None: return 0 l= self.maxDepth(root.left) r = self.maxDepth(root.right) return max(l,r)+1 3.翻转二叉树： 依旧递递的归：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 # Definition for a binary tree node. # class TreeNode: # def __init__(self, val=0, left=None, right=None): # self.val = val # self.left = left # self.right = right class Solution: def invertTree(self, root: Optional[TreeNode]) -\u0026gt; Optional[TreeNode]: def invert(node): if not node: return node.left,node.right = node.right,node.left invert(node.left) invert(node.right) invert(root) return root 4.对称二叉树 依旧递递的归\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 # Definition for a binary tree node. # class TreeNode: # def __init__(self, val=0, left=None, right=None): # self.val = val # self.left = left # self.right = right class Solution: def isSymmetric(self, root: Optional[TreeNode]) -\u0026gt; bool: def isduichen(lnode,rnode)-\u0026gt;bool: if not lnode and not rnode: return True if not (lnode and rnode): return False if lnode.val != rnode.val or lnode.val != rnode.val: return False return isduichen(lnode.left,rnode.right) and isduichen(lnode.right,rnode.left) if not root: return True return isduichen(root.left,root.right) 5.树的直径 算各个结点的左右深度之和并维持一个最大值\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 # Definition for a binary tree node. # class TreeNode: # def __init__(self, val=0, left=None, right=None): # self.val = val # self.left = left # self.right = right class Solution: def diameterOfBinaryTree(self, root: TreeNode) -\u0026gt; int: self.ans = 1 def depth(root): if not root: return 0 L = depth(root.left) R = depth(root.right) self.ans = max(self.ans, L + R + 1) return max(L, R) + 1 depth(root) return self.ans - 1 6.二叉树的层序遍历 题目描述： 给你二叉树的根节点 root ，返回其节点值的 层序遍历\n题解： 使用队列辅助\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 from collections import deque class Solution: def levelOrder(self, root: Optional[TreeNode]) -\u0026gt; List[List[int]]: if not root: return [] ans = [] queue = deque([root]) while queue: level = [] # 当前层有多少个节点 size = len(queue) # 只处理当前层的节点 for _ in range(size): node = queue.popleft() level.append(node.val) if node.left: queue.append(node.left) if node.right: queue.append(node.right) ans.append(level) return ans 7.将有序数组转换为二叉搜索树 题目描述： 给你一个整数数组 nums ，其中元素已经按 升序 排列，请你将其转换为一棵 平衡 二叉搜索树\n题解： 还是递归,我没招了\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution: def sortedArrayToBST(self, nums: List[int]) -\u0026gt; Optional[TreeNode]: def build(left, right): if left \u0026gt; right: return None mid = (left + right) // 2 root = TreeNode(nums[mid]) root.left = build(left, mid - 1) root.right = build(mid + 1, right) return root return build(0, len(nums) - 1) 8.验证二叉搜索树: 题目描述: 给你一个二叉树的根节点 root ，判断其是否是一个有效的二叉搜索树。\n有效 二叉搜索树定义如下：\n节点的左子树只包含 严格小于 当前节点的数。 节点的右子树只包含 严格大于 当前节点的数。 所有左子树和右子树自身必须也是二叉搜索树。 题解: 依旧递递的归 要注意的是:不能只比较父节点和直接子节点,要为每个节点设置一个范围(我第一遍就错在这了)\n1 2 3 4 5 6 7 8 9 10 11 12 # Definition for a binary tree node. # class TreeNode: # def __init__(self, val=0, left=None, right=None): # self.val = val # self.left = left # self.right = right class Solution: def isValidBST(self, root: Optional[TreeNode],left = -inf,right = inf) -\u0026gt; bool: if not root: return True return left \u0026lt; root.val \u0026lt; right and self.isValidBST(root.left,left,root.val) and self.isValidBST(root.right,root.val,right) 9.二叉搜索树中第K小的元素: 题目描述: 给定一个二叉搜索树的根节点 root ，和一个整数 k ，请你设计一个算法查找其中第 k 小的元素（k 从 1 开始计数）。\n题解: 我的第一想法:遍历并排序\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 # Definition for a binary tree node. # class TreeNode: # def __init__(self, val=0, left=None, right=None): # self.val = val # self.left = left # self.right = right class Solution: def kthSmallest(self, root: Optional[TreeNode], k: int) -\u0026gt; int: values = [] def visit(node): if not node: return values.append(node.val) visit(node.left) visit(node.right) visit(root) values.sort() return values[k-1] 经典解法:中序遍历加技术: BST的中序遍历是从小到大排序的,这个是迭代版本的,实质就是写中序遍历的过程,挺简单的\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution: def kthSmallest(self, root: Optional[TreeNode], k: int) -\u0026gt; int: stack = [] cur = root while True: while cur: stack.append(cur) cur = cur.left cur = stack.pop() k -= 1 if k == 0: return cur.val cur = cur.right 10.二叉树的右视图: 题目描述: 给定一个二叉树的 根节点 root，想象自己站在它的右侧，按照从顶部到底部的顺序，返回从右侧所能看到的节点值。\n题解: 超绝队列配合层序遍历:结果就是层序遍历的每一层的最后一个值\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 # Definition for a binary tree node. # class TreeNode: # def __init__(self, val=0, left=None, right=None): # self.val = val # self.left = left # self.right = right class Solution: def rightSideView(self, root: Optional[TreeNode]) -\u0026gt; List[int]: if not root: return [] quene = deque([root]) ans = [] while quene: level = [] for _ in range(len(quene)): node = quene.popleft() if node.left: quene.append(node.left) if node.right: quene.append(node.right) level.append(node.val) ans.append(level[-1]) return ans 11.二叉树展开为链表: 题目描述: 给你二叉树的根结点 root ，请你将它展开为一个单链表：\n展开后的单链表应该同样使用 TreeNode ，其中 right 子指针指向链表中下一个结点，而左子指针始终为 null 。 展开后的单链表应该与二叉树 先序遍历 顺序相同。 题解: 找左子树的最右节点,并把原本的右子树接到那里\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution: def flatten(self, root: Optional[TreeNode]) -\u0026gt; None: cur = root while cur: if cur.left: # 找左子树最右边的节点 pre = cur.left while pre.right: pre = pre.right # 原来的右子树接到这里 pre.right = cur.right # 左子树搬到右边 cur.right = cur.left cur.left = None cur = cur.right 12.从前序与中序遍历构造二叉树(考408的来了,你们聊) 题目描述: 给定两个整数数组 preorder 和 inorder ，其中 preorder 是二叉树的先序遍历， inorder 是同一棵树的中序遍历，请构造二叉树并返回其根节点。\n题解: 思路:从先序遍历里面找到根节点,然后在中序遍历里面找到该节点,然后左边为其左子树,右边是其右子树,迭代或者递归进行\n1 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 class Solution: def buildTree(self, preorder: List[int], inorder: List[int]) -\u0026gt; Optional[TreeNode]: if not preorder: return None # 1. preorder 第一个节点是根 root_val = preorder[0] root = TreeNode(root_val) # 2. 在 inorder 中找到根的位置 index = inorder.index(root_val) # 3. 划分左右子树 left_inorder = inorder[:index] right_inorder = inorder[index + 1:] # 左子树节点数量 left_size = len(left_inorder) # 4. 递归构建左右子树 root.left = self.buildTree( preorder[1:left_size + 1], left_inorder ) root.right = self.buildTree( preorder[left_size + 1:], right_inorder ) return root 13.路径总和: 题目描述: 给定一个二叉树的根节点 root ，和一个整数 targetSum ，求该二叉树里节点值之和等于 targetSum 的 路径 的数目。\n路径 不需要从根节点开始，也不需要在叶子节点结束，但是路径方向必须是向下的（只能从父节点到子节点）。\n题解: 暂留:\n14.二叉树的最近公共祖先 题目描述: 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。\n百度百科中最近公共祖先的定义为：“对于有根树 T 的两个节点 p、q，最近公共祖先表示为一个节点 x，满足 x 是 p、q 的祖先且 x 的深度尽可能大（一个节点也可以是它自己的祖先）。”\n题解: ","permalink":"https://tuanzi-wow.cn/leetcode%E5%88%B7%E9%A2%98%E8%AE%B0%E5%BD%95/hot100/hot100-binary-tree/","summary":"\u003ch3 id=\"1二叉树的中序遍历\"\u003e1.二叉树的中序遍历：\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv class=\"chroma\"\u003e\n\u003ctable class=\"lntable\"\u003e\u003ctr\u003e\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"lnt\"\u003e 1\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 2\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 3\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 4\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 5\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 6\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 7\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 8\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 9\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e10\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e11\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e12\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e13\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e14\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e15\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e16\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e17\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e# Definition for a binary tree node.\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e# class TreeNode:\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e#     def __init__(self, val=0, left=None, right=None):\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e#         self.val = val\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e#         self.left = left\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e#         self.right = right\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eclass Solution:\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    global ans = []\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    def inorderTraversal(self, root: Optional[TreeNode]) -\u0026gt; List[int]:\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            if not root:\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                return []\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            self.inorderTraversal(root.left)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            ans.append(root.val)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            self.inorderTraversal(root.right)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            return ans\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003ch3 id=\"2二叉树的最大深度\"\u003e2.二叉树的最大深度：\u003c/h3\u003e\n\u003cp\u003e递递的归\u003c/p\u003e","title":"HOT100 · 二叉树"},{"content":"第四章 大语言模型 4.1 什么是LLM 4.4.1 LLM的定义 一般来说,拥有更庞大的参数,在更海量的语料上进行预训练 广义的 LLM 一般覆盖了从十亿参数（如 Qwen-1.5B）到千亿参数（如 Grok-314B）的所有大型语言模型。只要模型展现出涌现能力，即在一系列复杂任务上表现出远超传统预训练模型（如 BERT、T5）的能力与潜力，都可以称之为 LLM。\n一般认为,GPT-3是LLM的开端,基于GPT-3通过预训练(Pretraining),监督微调(Supervised Fine-Tuning，SFT),强化学习和人类反馈(Reinforcement Learning with Human Feedback，RLHF)三阶段训练得到的ChatGPT更是主宰了LLM时代的到来\n4.1.2 LLM的能力 (1)涌现能力(Emergent Abilities) 区别LLM于传统的PLM最显著的特征就是LLM具备涌现能力: 指的是,在相同的架构和预训练任务下,某些能力在小型模型中不明显,但是在大型模型下非常突出(量变引起质变)\n(2)上下文学习(In-context Learning) 有GPT-3首次引入,具体而言,上下文学习指的是: 允许语言模型在提供自然语言指令或多个任务示例的情况下，通过理解上下文并生成相应输出的方式来执行任务，而无需额外的训练或参数更新\n而具备上下文学习能力的 LLM 往往无需进行高成本的额外训练或微调，而可以通过少数示例或是调整自然语言指令，来处理绝大部分任务，从而大大节省了算力和数据成本\n对于具备上下文学习的LLM来说,一般范式已经开始向Prompt Engineering(调整Prompt)来激发LLM的能力转变\n(3)指令遵循(Instruction Following) 指令微调的情况下,也就是使用自然语言描述的多任务数据进行微调 经过指令微调的LLM可以理解并遵循未见过的指令,具有强大的泛化能力\n(4)逐步推理(Step by Step Reasoning) LLM可以通过思维链(Chain of thought,CoT)推理策略,利用包含中间推理步骤的提示机制来解决复杂的逻辑推理任务\n4.1.3 LLM的特点 (1)多语言支持 训练的语料本事就是多语言的,但是目前英语的高质量语料仍是占据大多数\n(2)长文本支持 一:旋转位置编码（Rotary Positional Encoding，RoPE）,具有一定的长度外推能力 二:支持长文本\n(3)拓展多模态 图片等等\n(4)幻觉 LLM根据Prompt杜撰生成虚假,错误信息的表现 削弱幻觉的方式:Prompt中显式限制,通过RAG来指导生成,但是无法根除\n4.2 如何训练一个LLM 训练LLM的三个阶段 alt text\n训练一个完整的LLM需要三个阶段\u0026ndash;Pretrain,SFT和RLHF\n4.2.1 Pretrain 预训练:最核心也是工程量最大的一步 海量无监督文本对随机初始化的模型参数进行训练 由于主流的LLM都采用了Decoder-Only的类GPT框架(LLaMA框架),他们的训练任务为经典预训练任务\u0026ndash;因果语言模型(CLM)\nLLM预训练于传统模型的预训练的最核心差异在于:预训练的体量和资源消耗 更大的参数量和更海量的语料 训练语料的量: 1.根据由 OpenAI 提出的 Scaling Law：C ~ 6ND，其中 C 为计算量，N 为模型参数，D 为训练的 token 数，可以实验得出训练 token 数应该是模型参数的 1.7倍 2.而 LLaMA 更是进一步提出，使用 20倍 token 来训练模型能达到效果最优\n分布式训练框架是LLM训练必不可少的组成部分 核心思路:数据并行和模型并行 数据并行:训练模型的尺寸可以被单个 GPU 内存容纳 但是:由于增大训练的 batch_size 会增大显存开销，无法使用较大的 batch_size 进行训练;训练数据量非常大，使用单张 GPU 训练时长难以接受\n但是，当 LLM 扩大到上百亿参数，单张 GPU 内存往往就无法存放完整的模型参数。如图4.3所示，在这种情况下，可以将模型拆分到多个 GPU 上，每个 GPU 上存放不同的层或不同的部分，从而实现模型并行\n在此思想基础上,还演化出了其他的方式,例如: 张量并行、3D 并行、ZeRO（Zero Redundancy Optimizer，零冗余优化器) 主流的分布式训练框架包括:Deepspeed、Megatron-LM、ColossalAI 等，其中，Deepspeed 使用面最广\nDeepSpeed的核心策略是ZeRO和CPU-offload ZeRO是一种显存优化的数据并行方案,其核心思想是:优化数据并行的每张卡的显存占用,从而实现对更大规模模型的支持 它将模型训练阶段每张卡被占用的显存分为两类:\n模型状态（Model States），包括模型参数、模型梯度和优化器 Adam 的状态参数。假设模型参数量为 1M，一般来说，在混合精度训练的情况下，该部分需要 16M 的空间进行存储，其中 Adam 状态参数会占据 12M 的存储空间。 剩余状态（Residual States），除了模型状态之外的显存占用，包括激活值、各种缓存和显存碎片 针对上述显存占用，ZeRO 提出了三种不断递进的优化策略：\nZeRO-1，对模型状态中的 Adam 状态参数进行分片，即每张卡只存储 1NN1 的 Adam 状态参数，其他参数仍然保持每张卡一份。 ZeRO-2，继续对模型梯度进行分片，每张卡只存储 1NN1 的模型梯度和 Adam 状态参数，仅模型参数保持每张卡一份。 ZeRO-3，将模型参数也进行分片，每张卡只存储 1NN1 的模型梯度、模型参数和 Adam 状态参数。 随着分片的参数量不断增加，每张卡需要占用的显存也不断减少。当然，分片的增加也就意味着训练中通信开销的增加,每张卡的 GPU 利用率 ZeRO-1 最高而 ZeRO-3 最低\n另一大挑战:训练数据本身:LLM的大部分知识都是在预训练中获得的,因此预训练的语料应该组织多种来源的数据并且以一定比例进行混合 事实上，数据配比向来是预训练 LLM 的“核心秘籍”，不同的配比往往会相当大程度影响最终模型训练出来的性能 问题:目前高质量的语料大部分还是集中在英文范畴\n预训练数据的处理与清洗也是一个重要环节:预训练数据的质量往往比体量更重要,\n预训练数据处理一般包含以下流程:\n文档准备。由于海量预训练语料往往是从互联网上获得，一般需要从爬取的网站来获得自然语言文档。文档准备主要包括 URL 过滤（根据网页 URL 过滤掉有害内容）、文档提取（从 HTML 中提取纯文本）、语言选择（确定提取的文本的语种）等。 语料过滤。语料过滤的核心目的是去除低质量、无意义、有毒有害的内容，例如乱码、广告等。语料过滤一般有两种方法：基于模型的方法，即通过高质量语料库训练一个文本分类器进行过滤；基于启发式的方法，一般通过人工定义 web 内容的质量指标，计算语料的指标值来进行过滤。 语料去重。实验表示，大量重复文本会显著影响模型的泛化能力，因此，语料去重即删除训练语料中相似度非常高的文档，也是必不可少的一个步骤。去重一般基于 hash 算法计算数据集内部或跨数据集的文档相似性，将相似性大于指定阈值的文档去除；也可以基于子串在序列级进行精确匹配去重。 目前有很多经过处理的高质量预训练语料专用于预训练数据处理的框架\n有基于 LLaMA 思路收集、清洗的预训练数据集RedPajama-1T，以及在 RedPajama 基础上进行筛选去重的SlimPajama-627B数据集，实验证明高质量的 627B Slimpajama 数据集能够获得比 1T 的 RedPajama 数据集更好的效果。 (客观论证了预训练数据的质量比体量更重要)\n4.2.2 SFT(有监督微调) 通过选择训练模型的\u0026quot;通用指令遵循能力\u0026quot;,也就是一般通过指令微调的方式来进行SFT\nSFT的主要目标就是让模型从多种类型,多种风格的指令中获得泛化的指令遵循能力,也就是能够理解和回复用户的指令 因此:SFT的数据质量和数据配比也是决定模型指令遵循能力的重要因素\n首先是指令数据量以及覆盖范围: 需要收集类别各异的用户指令和对应回复对LLM进行训练,为了使得泛化的指令遵循能力,需要在训练数据集中覆盖多种类型的任务指令,也需要较大的数据量\n不同类型的指令数据中间的配比也是LLM训练的一大挑战:\n高质量的指令数据集具有较高的获取难度。不同于预训练使用的无监督语料，SFT 使用的指令数据集是有监督语料，除去设计广泛、合理的指令外，还需要对指令回复进行人工标注，并保证标注的高质量\n为降低数据成本，部分学者提出了使用 ChatGPT 或 GPT-4 来生成指令数据集的方法。例如，经典的开源指令数据集 Alpaca就是基于一些种子 Prompt，通过 ChatGPT 生成更多的指令并对指令进行回复来构建的。\n一般 SFT 所使用的指令数据集包括以下三个键：\n1 2 3 4 5 { \u0026#34;instruction\u0026#34;:\u0026#34;即输入的用户指令\u0026#34;, \u0026#34;input\u0026#34;:\u0026#34;执行该指令可能需要的补充输入，没有则置空\u0026#34;, \u0026#34;output\u0026#34;:\u0026#34;即模型应该给出的回复\u0026#34; } 同时，为使模型能够学习到和预训练不同的范式，在 SFT 的过程中，往往会针对性设置特定格式。例如，LLaMA 的 SFT 格式为：\n1 ### Instruction:\\n{{content}}\\n\\n### Response:\\n 其中的 content 即为具体的用户指令，也就是说，对于每一个用户指令，将会嵌入到上文的 content 部分，这里的用户指令不仅指上例中的 “instruction”，而是指令和输入的拼接，即模型可以执行的一条完整指令。例如，针对上例，LLaMA 获得的输入应该是：\n1 ### Instruction:\\n将下列文本翻译成英文：今天天气真好\\n\\n### Response:\\n 注意，因为指令微调本质上仍然是对模型进行 CLM 训练，只不过要求模型对指令进行理解和回复而不是简单地预测下一个 token，所以模型预测的结果不仅是 output，而应该是 input + output，只不过 input 部分不参与 loss 的计算，但回复指令本身还是以预测下一个 token 的形式来实现的。\n模型是否支持多轮对话，与预训练是没有关系的。事实上，模型的多轮对话能力完全来自于 SFT 阶段。如果要使模型支持多轮对话，我们需要在 SFT 时将训练数据构造成多轮对话格式，让模型能够利用之前的知识来生成回答\n构造多轮对话样本一般有三种方式: 1.直接将最后一次模型回复作为输出,前面所有历史会话作为输入,直接拟合最后一次回复 2.将N轮多花构造成N个样本 3.直接要求模型预测每一轮对话的输出\n第一种方式会丢失大量中间信息，第二种方式造成了大量重复计算，只有第三种方式是最合理的多轮对话构造。\n我们之所以可以以第三种方式来构造多轮对话样本，是因为 LLM 本质还是进行的 CLM 任务，进行单向注意力计算，因此在预测时会从左到右依次进行拟合，前轮的输出预测不会影响后轮的预测。目前，绝大部分 LLM 均使用了多轮对话的形式来进行 SFT\n4.2.3 RLHF(人类反馈强化学习) 利用强化学习来训练LLM的关键步骤 RLHF往往被认为ChatGPT相较于GPT3最核心的突破 从功能上出发,我们可以将LLM的训练过程分为预训练与对齐两个阶段\n预训练的核心功能是赋予模型海量知识;二对齐,则是让模型与人类价值观一致,从而输出人类希望输出的内容 SFT是让LLM和人类指令对齐,从而拥有指令遵循的能力;而RLHF则是从更深层次使LLM与人类价值观对齐,从而达到安全有用无害的和核心标准\nChatGPT在技术报告中的对齐分为三个阶段:后面两个阶段训练RM和PPO训练,就是RLHF的步骤 alt text\nRLHF的思路:引入强化学习的技术,通过实时的人类反馈零LLM能够给出更令人满意的回复 强化学习是有别于监督学习的另一种机器学习的方法,主要由两个部分构成:智能体和环境\npretrain传授基础知识;SFT教他怎么做题;RLHF类似于真正的训练\nRLHF分为两个步骤:训练RM和PPO训练 RM: Reward Model 即奖励模型 本质上是一个文本分类模型\nRM，Reward Model，即奖励模型。RM 是用于拟合人类偏好，来给 LLM 做出反馈的。在强化学习的训练中，对于 LLM 的每一个回复，RM 会进行打分，这个打分反映了生成回复符合人类偏好的程度。然后 LLM 会根据强化学习的原理，基于 RM 的打分来进行优化训练。所以，RM 本质上是一个文本分类模型，对于一个文本输出一个标量奖励，和文本分类任务中的隐藏层输出非常类似。在具体实现上，RM 也往往就是传统的 LLM 架构（或 BERT 架构）加上一层分类层，和用于文本分类的 LLM 架构完全一致，只不过使用隐藏层输出而不是最后的分类输出而已。\n但是，在训练 RM 时，我们往往并不直接使用文本及其对应的标量奖励来对 RM 进行训练。因为要对齐人类偏好，RM 训练的偏好数据往往是由人工标注的。但是，由于标注者之间往往也存在价值观差异，数值形式的标量奖励往往会将这些差异放大，从而导致在训练过程中对同样程度的回复奖励不一致，模型难以拟合到正确的标量奖励。因此，我们往往对同一个 completion 下的不同回复进行排名，再将排名转化为奖励。\n例如，我们的训练数据往往形如：\n1 2 3 4 5 { \u0026#34;prompt\u0026#34;:\u0026#34;如果你打算从商店偷东西，你觉得早上好还是晚上好？\u0026#34;, \u0026#34;chosen\u0026#34;:\u0026#34;这是违法的事情，我不能提供建议\u0026#34;, \u0026#34;rejected\u0026#34;:\u0026#34;考虑晚上的人口贩运和监控摄像头的差别是件好事。夜间时间可能更有利于避免监控摄像头,但晚上的商店雇员会更能看见你。另一方面,由于白天通常不太忙,因此更容易避免被其他人注意到。无论如何,必须密切注意商店雇员和所有顾客的行为。他们也许能够看见你,即使他们不是直接面对你。为了安全起见,重要的是要密切注意商店里的一切事情,而不是不小心。\u0026#34; }Copy to clipboardErrorCopied 其中，prompt 是用户的问题，chosen 是应该对齐的、符合人类偏好的回答，rejected 是不符合人类偏好的回答。在训练中，prompt 将和 chosen 以及 rejected 分别拼接起来，形成 chosen_example 和 rejected_example，然后分别进入模型通过前向传播输出一个标量奖励。然后模型会通过最大化 chosen_example 和 rejected_example 的标量差异来计算 loss，并进行反向传播完成训练。\n值得注意的是，RM 训练使用的模型往往和最后的 LLM 大小不同。例如 OpenAI 使用了 175B 的 LLM 和 6B 的 RM。同时，RM 使用的模型可以是经过 SFT 之后的 LM，也可以是基于偏好数据从头训练的 RM。哪一种更好，至今尚没有定论。\n在完成 RM 训练之后，就可以使用 PPO 算法来进行强化学习训练。PPO，Proximal Policy Optimization，近端策略优化算法，是一种经典的 RL 算法。事实上，强化学习训练时也可以使用其他的强化学习算法，但目前 PPO 算法因为成熟、成本较低，还是最适合 RLHF 的算法。\n在具体 PPO 训练过程中，会存在四个模型。如图4.5所示，两个 LLM 和两个 RM。两个 LLM 分别是进行微调、参数更新的 actor model 和不进行参数更新的 ref model，均是从 SFT 之后的 LLM 初始化的。两个 RM 分别是进行参数更新的 critic model 和不进行参数更新的 reward model，均是从上一步训练的 RM 初始化的。\nalt text\n图4.5 PPO 训练流程\n如上图，使用 PPO 算法的强化学习训练过程如下：\n从 SFT 之后的 LLM 初始化两个模型分别作为 Actor Model 和 Ref Model；从训练的 RM 初始化两个模型分别作为 Reward Model 和 Critic Model； 输入一个 Prompt，Actor Model 和 Ref Model 分别就 Prompt 生成回复； Actor Response 和 Ref Response 计算 KL 散度： rKL=−θKLDKL(πPPO(y∣x)∣∣πbase(y∣x))rKL=−θKLDK**L(πPP**O(y∣x)∣∣πbase(y∣x)) 其中， πPPO(y∣x)πPP**O(y∣x) 即为 Actor Model 的输出，而 πbase(y∣x)πbase(y∣x) 即为 Ref Model 的输出， θKLDKLθKLDK**L 即是计算 KL 散度的方法； Actor Response 分别输入到 Reward Model 和 Critic Model 进行打分，其中，Reward Model 输出的是回复对应的标量奖励，Critic Model 还会输出累加奖励（即从i位置到最后的累积奖励）； 计算的 KL 散度、两个模型的打分均输入到奖励函数中，计算奖励： loss=−(klctl⋅rKL+γ⋅Vt+1−Vt)log⁡P(At∣Vt)l**oss=−(klc**tl⋅rKL+γ⋅V**t+1−V**t)logP(A**t∣V**t) ，这里的 klctlklc**tl 是控制 KL 散度对结果影响的权重参数， γγ 是控制下一个时间（也就是样本）打分对结果影响的权重参数， VtV**t 是 Critic Model 的打分输出， AtA**t 则是 Reward Model 的打分输出; 根据奖励函数分别计算出的 actor loss 和 critic loss，更新 Actor Model 的参数和 Critic Model 的参数；注意，Actor Model 和 Critic Model 的参数更新方法是不同的，此处就不再一一赘述了，感兴趣的读者可以深入研究强化学习的相关理论。 在上述过程中，因为要使用到四个模型，显存占用会数倍于 SFT。例如，如果我们 RM 和 LLM 都是用 7B 的体量，PPO 过程中大概需要 240G（4张 80G A100，每张卡占用 60G）显存来进行模型加载。那么，为什么我们需要足足四个模型呢？Actor Model 和 Critic Model 较为容易理解，而之所以我们还需要保持原参数不更新的 Ref Model 和 Reward Model，是为了限制模型的更新不要过于偏离原模型以至于丢失了 Pretrain 和 SFT 赋予的能力。\n当然，如此大的资源占用和复杂的训练过程，使 RLHF 成为一个门槛非常高的阶段。也有学者从监督学习的思路出发，提出了 DPO（Direct Preference Optimization，直接偏好优化），可以低门槛平替 RLHF。DPO 的核心思路是，将 RLHF 的强化学习问题转化为监督学习来直接学习人类偏好。DPO 通过使用奖励函数和最优策略间的映射，展示了约束奖励最大化问题完全可以通过单阶段策略训练进行优化，也就是说，通过学习 DPO 所提出的优化目标，可以直接学习人类偏好，而无需再训练 RM 以及进行强化学习。由于直接使用监督学习进行训练，DPO 只需要两个 LLM 即可完成训练，且训练过程相较 PPO 简单很多，是 RLHF 更简单易用的平替版本\n","permalink":"https://tuanzi-wow.cn/llm/llm-large-language-model/","summary":"\u003ch1 id=\"第四章-大语言模型\"\u003e第四章 大语言模型\u003c/h1\u003e\n\u003ch2 id=\"41-什么是llm\"\u003e4.1 什么是LLM\u003c/h2\u003e\n\u003ch3 id=\"441-llm的定义\"\u003e4.4.1 LLM的定义\u003c/h3\u003e\n\u003cp\u003e一般来说,拥有更庞大的参数,在更海量的语料上进行预训练\n广义的 LLM 一般覆盖了从\u003cstrong\u003e十亿参数\u003c/strong\u003e（如 Qwen-1.5B）到\u003cstrong\u003e千亿参数\u003c/strong\u003e（如 Grok-314B）的所有大型语言模型。只要模型展现出\u003cstrong\u003e涌现能力\u003c/strong\u003e，即在一系列复杂任务上表现出远超传统预训练模型（如 BERT、T5）的能力与潜力，都可以称之为 LLM。\u003c/p\u003e","title":"LLM · 大语言模型"},{"content":"提示词示例 文本概括: 信息提取: 简单地指示模型应该做什么\n问答 改善提示的格式\n结合指令,上下文,输入和输出指示\n文本分类 提供输入数据和示例\n具体性防止歧义或者其他情况\n对话 角色提示\n代码生成 推理 ","permalink":"https://tuanzi-wow.cn/promptengineering/prompt-examples/","summary":"\u003ch1 id=\"提示词示例\"\u003e提示词示例\u003c/h1\u003e\n\u003ch2 id=\"文本概括\"\u003e文本概括:\u003c/h2\u003e\n\u003ch2 id=\"信息提取\"\u003e信息提取:\u003c/h2\u003e\n\u003cp\u003e简单地指示模型应该做什么\u003c/p\u003e\n\u003ch2 id=\"问答\"\u003e问答\u003c/h2\u003e\n\u003cp\u003e改善提示的格式\u003c/p\u003e\n\u003cp\u003e结合指令,上下文,输入和输出指示\u003c/p\u003e\n\u003ch2 id=\"文本分类\"\u003e文本分类\u003c/h2\u003e\n\u003cp\u003e提供输入数据和示例\u003c/p\u003e\n\u003cp\u003e具体性防止歧义或者其他情况\u003c/p\u003e\n\u003ch2 id=\"对话\"\u003e对话\u003c/h2\u003e\n\u003cp\u003e\u003cstrong\u003e角色提示\u003c/strong\u003e\u003c/p\u003e\n\u003ch2 id=\"代码生成\"\u003e代码生成\u003c/h2\u003e\n\u003ch2 id=\"推理\"\u003e推理\u003c/h2\u003e","title":"Prompt Engineering · 提示词示例"},{"content":"设计提示的通用技巧 从简单开始 remember:这是一个迭代的过程 简单的提示词,并逐渐添加更多的元素和上下文\n涉及不同子任务的大型任务,将任务分解为更简单的子任务\n指令 你可以使用命令来指示模型执行各种简单任务，例如“写入”、“分类”、“总结”、“翻译”、“排序”等，从而为各种简单任务设计有效的提示。\n请记住，你还需要进行大量实验以找出最有效的方法。以不同的关键词（keywords），上下文（contexts）和数据（data）试验不同的指令（instruction），看看什么样是最适合你特定用例和任务的。通常，上下文越具体和跟任务越相关则效果越好。在接下来的指南中，我们将讨论样例和添加更多上下文的重要性。\n有些人建议将指令放在提示的开头。另有人则建议是使用像“###”这样的清晰分隔符来分隔指令和上下文\nps:有点扯\n具体性 要非常具体地说明你希望模型执行的指令和任务。提示越具描述性和详细，结果越好 信息的多少和完善程度会影响响应的可靠性\n注意提示的长度\n避免不明确 具体且直接是最好的\n做什么还是不做什么 避免说不做什么,而是说要做什么\n","permalink":"https://tuanzi-wow.cn/promptengineering/prompt-design-tips/","summary":"\u003ch1 id=\"设计提示的通用技巧\"\u003e设计提示的通用技巧\u003c/h1\u003e\n\u003ch2 id=\"从简单开始\"\u003e从简单开始\u003c/h2\u003e\n\u003cp\u003eremember:这是一个迭代的过程\n简单的提示词,并逐渐添加更多的元素和上下文\u003c/p\u003e\n\u003cp\u003e涉及不同子任务的大型任务,将任务分解为更简单的子任务\u003c/p\u003e\n\u003ch2 id=\"指令\"\u003e指令\u003c/h2\u003e\n\u003cp\u003e你可以使用命令来指示模型执行各种简单任务，例如“写入”、“分类”、“总结”、“翻译”、“排序”等，从而为各种简单任务设计有效的提示。\u003c/p\u003e","title":"Prompt Engineering · 设计提示的通用技巧"},{"content":"模型设置 在使用提示词时,你通常会配置一些参数以获得不同的提示结果.调整这些设置对于响应的可靠性非常重要\n常见的一些参数设置及其作用 Temperature: 结果的多样化和创造性 temperature的参数值越小,更容易返回一个确定的结果 调小temperature是在增加其他可能的token的权重 实际应用方面: 对于质量保障(QA)等任务,我们可以设置较低的temperature 对于创造性任务例如创作:适当提高temperature参数值\nTop_p: 也可以控制返回结果的确定性,类似于temperature 与 temperature 一起称为核采样（nucleus sampling）的技术\n使用Top P意味着只有词元集合（tokens）中包含top_p概率质量的才会被考虑用于响应，因此较低的top_p值会选择最有信心的响应。这意味着较高的top_p值将使模型考虑更多可能的词语，包括不太可能的词语，从而导致更多样化的输出。\n一般建议是改变 Temperature 和 Top P 其中一个参数就行，不用两个都调整。\nMax Length: 生成文本的最大token数 控制成本;防止生成冗长或不相关响应\nStop Sequence 为一个字符串,阻止模型生成token,，指定 stop sequences 是控制大模型响应长度和结构的另一种方法 例如，您可以通过添加 “11” 作为 stop sequence 来告诉模型生成不超过 10 个项的列表\nFrequency Penalty 这个惩罚和 token 在响应和提示中已出现的次数成比例， frequency penalty 越高，某个词再次出现的可能性就越小，这个设置通过给 重复数量多的 Token 设置更高的惩罚来减少响应中单词的重复\nPresence Penalty presence penalty 也是对重复的 token 施加惩罚，但与 frequency penalty 不同的是，惩罚对于所有重复 token 都是相同的。出现两次的 token 和出现 10 次的 token 会受到相同的惩罚。\n此设置可防止模型在响应中过于频繁地生成重复的词。 如果您希望模型生成多样化或创造性的文本，您可以设置更高的 presence penalty，如果您希望模型生成更专注的内容，您可以设置更低的 presence penalty\n一般建议是改变 frequency penalty 和 presence penalty 其中一个参数就行，不要同时调整两个。\n","permalink":"https://tuanzi-wow.cn/promptengineering/llm-settings/","summary":"\u003ch1 id=\"模型设置\"\u003e模型设置\u003c/h1\u003e\n\u003cp\u003e在使用提示词时,你通常会配置一些参数以获得不同的提示结果.调整这些设置对于响应的可靠性非常重要\u003c/p\u003e\n\u003ch3 id=\"常见的一些参数设置及其作用\"\u003e常见的一些参数设置及其作用\u003c/h3\u003e\n\u003ch4 id=\"temperature\"\u003eTemperature:\u003c/h4\u003e\n\u003cp\u003e结果的多样化和创造性\ntemperature的参数值越小,更容易返回一个确定的结果\n调小temperature是在增加其他可能的token的权重\n实际应用方面:\n对于质量保障(QA)等任务,我们可以设置较低的temperature\n对于创造性任务例如创作:适当提高temperature参数值\u003c/p\u003e","title":"Prompt Engineering · 模型设置"},{"content":"定义： 模块是包含 Python 定义和语句的文件。其文件名是模块名加后缀名 .py 。在模块内部，通过全局变量 __name__ 可以获取模块名（即字符串）。\n导入模块： 1 import name(模块的真实名字) 本质将模块名称添加到namespeac而非把定义的函数添加进去\n6.1 模块详解： 模块包含可执行语句及函数定义。这些语句用于初始化模块，且仅在 import 语句 第一次 遇到模块名时执行。 每个模块都有它自己的私有符号表，该表被定义在该模块里的所有函数当作全局符号表使用。 您可以使用与引用模块函数相同的语法去访问一个模块的全局变量，即 modname.itemname\n导入其他模块：import 被放置于一个模块的最高层级，则被导入的模块名称会被添加到该模块的全局命名空间。 import的变式1：将来自某个模块的名称直接导入到导入方的命名空间里面\n1 2 3 from fibo import fib, fib2 fib(500) 0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 变式2：导入模块内定义的所有名称（不包含以_开头的名称）\n1 2 3 from fibo import * fib(500) 0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 不建议：可能会覆盖已经定义的名称\n变式3：模块名后接as\n1 2 import fibo from fib fib.fib(500) 或者变式1，3结合\n1 2 3 from fibo import fib as fibonacci fibonacci(500) 0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 备注 每次解释器会话只导入一次模块，若模块内容更改，需要重启解释器 如仅交互测试一个模块，可以使用importlib.reload()，例如 import importlib; importlib.reload(modulename)。\n6.1.1.以脚本方式执行模块 最常见命令：\n1 python fibo.py \u0026lt;arguments\u0026gt;（这个参数的必须的） 导入模块的代码，但是会将_name_命名为\u0026quot;main\u0026quot;(这里前面和后面都有一个__，但是因为md原因导致没有了)\n例如\n1 2 python fibo.py 50 0 1 1 2 3 5 8 13 21 34 相当于\n1 2 3 if __name__ == \u0026#34;__main__\u0026#34;: import sys fib(int(sys.argv[1])) 该文件既可作为模块又可以作为脚本 而像下面这种导入其他模块则不会\n1 import fibo 用于为模块提供一个便捷的用户接口，或用于测试\n6.1.2.模块的搜索路径 当导入一个名为 spam 的模块时，解释器首先会搜索具有该名称的内置模块。 这些模块的名称在 sys.builtin_module_names 中列出。 如果未找到，它将在变量 sys.path 所给出的目录列表中搜索名为 spam.py 的文件。 sys.path 是从这些位置初始化的:\n被命令行直接运行的脚本所在的目录（或未指定文件时的当前目录）。 PYTHONPATH （目录列表，与 shell 变量 PATH 的语法一样）。 依赖于安装的默认值（按照惯例包括一个 site-packages 目录，由 site 模块处理）。 更多细节请参阅 sys.path 模块搜索路径的初始化。\n备注 在支持符号链接的文件系统中，“被命令行直接运行的脚本所在的目录”是符号链接最终指向的目录。换句话说，符号链接所在的目录并 没有 被添加至模块搜索路径。\n初始化后，Python 程序可以更改 sys.path。脚本所在的目录先于标准库所在的路径被搜索。这意味着，脚本所在的目录如果有和标准库同名的文件，那么加载的是该目录里的，而不是标准库的。这一般是一个错误，除非这样的替换是你有意为之。详见 标准模块。\n6.1.3.”已编译的“Python文件 模块的编译版本存放在\u0026ndash;pycache__目录中，文件名为module.version.pyc,\nversion:对编译文件格式进行编码，一般是版本号 可以让不同版本的Python编译的模块可以共存 例子：CPython 的 3.3 发行版中，spam.py 的编译版本缓存为 __pycache__/spam.cpython-33.pyc\nPython会自动检查编译版本和源码的修改日期来查看编译版是否过期，是否需要重新编译 编译模块与平台无关 Python不检查缓存的两种情况 1.从命令行直接载入的模块：每次重新编译且不存储结果 2.没有源模块 隐藏源代码：将所有源代码编程编译之后的版本，并储存在源目录而非缓存目录中）\nps： .pyc的读取未必比.py快，只是加载速度更快 compileall 模块可以为一个目录下的所有模块创建 .pyc 文件。\n6.2.标准模块 Python自带的，在Python的库参考中另外描述 这些模块集是一个配置选项，并且还依赖于底层的操作系统 例如winreg只在WIndows系统上提供 但是sys模块内嵌到每一个Python解释器，sys.psq和sys.ps2变量定义了一些字符，可以作为主提示符和辅助提示符 变量的sys.path：字符串列表，用于确定解释器的模块搜索路径 从环境变量PYTHONPATH提取默认路径进行初始化，若未设置PYTHONPATH，则使用内置的默认路径 可以通过标准列表操作修改该变量\n1 2 import sys sys.path.append(\u0026#39;/ufs/guido/lib/python\u0026#39;) 6.3.dir()函数 查找模块定义的名称，返回值：经过排序的字符串列表 没有参数时： 返回值：当前已经定义的名称 包括：变量，模块，函数\u0026hellip;\u0026hellip; 但是不会列出内置函数和变量的名称 这些内容的定义在标准模块 builtins 中：\n6.4. 包 通过”带点号模块名“来狗仔Python模块命名空间的一种方式 可以让不同包的作者不用担心彼此的全局变量名，比如模块名冲突\n导入包时，Python 搜索 sys.path 里的目录，查找包的子目录。\n需要有 __init__.py 文件才能让 Python 将包含该文件的目录当作包来处理（除非使用 namespace package，这是一个相对高级的特性）。 这可以防止重名的目录如 string 在无意中屏蔽后继出现在模块搜索路径中的有效模块。 在最简单的情况下，__init__.py 可以只是一个空文件，但它也可以执行包的初始化代码或设置 __all__ 变量，这将在稍后详细描述。\n还可以从包中导入单个模块，例如：\n1 import sound.effects.echo 这将加载子模块 sound.effects.echo。 它必须通过其全名来引用。\n1 sound.effects.echo.echofilter(input, output, delay=0.7, atten=4) 另一种导入子模块的方法是 ：\n1 from sound.effects import echo 这也会加载子模块 echo，并使其不必加包前缀，因此可按如下方式使用:\n1 echo.echofilter(input, output, delay=0.7, atten=4) Import 语句的另一种变体是直接导入所需的函数或变量：\n1 from sound.effects.echo import echofilter 同样，这将加载子模块 echo，但这使其函数 echofilter() 直接可用:\n1 echofilter(input, output, delay=0.7, atten=4) 注意，使用 from package import item 时，item 可以是包的子模块（或子包），也可以是包中定义的函数、类或变量等其他名称。import 语句首先测试包中是否定义了 item；如果未在包中定义，则假定 item 是模块，并尝试加载。如果找不到 item，则触发 ImportError 异常。\n相反，使用 import item.subitem.subsubitem 句法时，除最后一项外，每个 item 都必须是包；最后一项可以是模块或包，但不能是上一项中定义的类、函数或变量。\n6.4.1. 从包中导入* 提供包的显式索引\n导入在init.py 代码定义了列表 all 发布新版本时，应更新该列表 例如：sound/effects/__init__.py 文件可以包含以下代码：\n1 __all__ = [\u0026#34;echo\u0026#34;, \u0026#34;surround\u0026#34;, \u0026#34;reverse\u0026#34;] 这意味着 from sound.effects import * 将导入 sound.effects 包的三个命名子模块。请注意子模块可能会受到本地定义名称的影响。 例如，如果你在 sound/effects/__init__.py 文件中添加了一个 reverse 函数，from sound.effects import * 将只导入 echo 和 surround 这两个子模块，但 不会 导入 reverse 子模块，因为它被本地定义的 reverse 函数所遮挡:\n1 2 3 4 5 6 7 8 __all__ = [ \u0026#34;echo\u0026#34;, # 指向 \u0026#39;echo.py\u0026#39; 文件 \u0026#34;surround\u0026#34;, # 指向 \u0026#39;surround.py\u0026#39; 文件 \u0026#34;reverse\u0026#34;, # !!! 现在指向 \u0026#39;reverse\u0026#39; 函数 !!! ] def reverse(msg: str): # \u0026lt;-- 此名称将覆盖 \u0026#39;reverse.py\u0026#39; 子模块 return msg[::-1] # 针对 \u0026#39;from sound.effects import *\u0026#39; 的情况 没有定义____all____: 有点没懂\n6.4.2. 相对导入 使用前导符号.来完成 一般用于子模块\n","permalink":"https://tuanzi-wow.cn/python/python-06-modules/","summary":"\u003ch2 id=\"定义\"\u003e定义：\u003c/h2\u003e\n\u003cp\u003e模块是包含 Python 定义和语句的文件。其文件名是模块名加后缀名 \u003ccode\u003e.py\u003c/code\u003e 。在模块内部，通过全局变量 \u003ccode\u003e__name__\u003c/code\u003e 可以获取模块名（即字符串）。\u003c/p\u003e\n\u003ch3 id=\"导入模块\"\u003e导入模块：\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv class=\"chroma\"\u003e\n\u003ctable class=\"lntable\"\u003e\u003ctr\u003e\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"lnt\"\u003e1\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eimport name(模块的真实名字)\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003e本质将模块名称添加到namespeac而非把定义的函数添加进去\u003c/p\u003e","title":"6. 模块"},{"content":"7.1.更加复杂的输出格式 格式化字符串字面值 在字符串开头的引号/三引号之前加f/F 特性：可以通过{}在字符之间输入引用的变量，或者字面值的Python表达式 例子如下：\n1 2 3 4 year = 2016 event = \u0026#39;Referendum\u0026#39; f\u0026#39;Results of the {year} {event}\u0026#39; \u0026#39;Results of the 2016 Referendum\u0026#39; str.format（）: 特性：1.同上，但你需要提供详细的格式化指令和待格式化的信息 例子：\n1 2 3 4 5 yes_votes = 42_572_654 total_votes = 85_705_149 percentage = yes_votes / total_votes \u0026#39;{:-9} YES votes {:2.2%}\u0026#39;.format(yes_votes, percentage) \u0026#39; 42572654 YES votes 49.67%\u0026#39; 解释： 注意此处的输出 -9：\u0026quot;-\u0026ldquo;表示只有在复数是才显示负号，+表示总是显示正负号 例如 {\u0026rsquo;\u0026rsquo;:+\u0026rsquo;\u0026rsquo;}.format(100) 输出+100; {\u0026rsquo;\u0026rsquo;:+\u0026rsquo;\u0026rsquo;}.format(-100) 输出-100; {\u0026rsquo;\u0026rsquo;:-\u0026rsquo;\u0026rsquo;}.format(100) 输出100; {\u0026rsquo;\u0026rsquo;:-\u0026rsquo;\u0026rsquo;}.format(-100) 输出-100; \u0026lsquo;9\u0026rsquo;表示最小宽度，没有达到则需要填充，比如上面只有八位，在前面填充了一个空格； {:2.2%} ： 第一个2也表示整数部分最小宽度，因达到两位所以无影响 第二个2表示小数点后保留两位； 百分号表示用%表示\nstr()和repr():把值转化成字符串 不需要花哨输出，用于快速显示变量进行调试\nstring 模块通过 string.Template 提供了一种基于正则表达式的简单模板方法支持。 这种方式为字符串中的值替换提供了另一种途径：使用类似 $x 的占位符，并将其替换为字典中的对应值。该语法使用简便，但在格式控制方面提供的功能较为有限。\n7.1.1.格式化字符串字面值 字符串前夹f或F，通过{expression}表达式，把Python变量引入字符串中 格式说明符：可选，便于更好控制格式化值的方式，写在表达式的后面 例1：将pi舍入到小数点后三维 {math.pi:.3f} 例2：再：传递整数，表示最小字符宽度，常于用列对齐 常用于格式化的修饰符及其作用： '!a' 应用 ascii() ，'!s' 应用 str()，'!r' 应用 repr()： =的作用：{expression = }将其拓展为 expression = value\n7.1.2.字符串format方法 基本用法： 例1：\n1 2 print(\u0026#39;We are the {} who say \u0026#34;{}!\u0026#34;\u0026#39;.format(\u0026#39;knights\u0026#39;, \u0026#39;Ni\u0026#39;)) We are the knights who say \u0026#34;Ni!\u0026#34; {}替换为传递给format方法的对象： {}内的数字：表示传递的对象的位置，例如\n1 2 3 4 print(\u0026#39;{0} and {1}\u0026#39;.format(\u0026#39;spam\u0026#39;, \u0026#39;eggs\u0026#39;)) spam and eggs print(\u0026#39;{1} and {0}\u0026#39;.format(\u0026#39;spam\u0026#39;, \u0026#39;eggs\u0026#39;)) eggs and spam 通过关键字参数名引入value\n例：\n1 2 3 print(\u0026#39;This {food} is {adjective}.\u0026#39;.format( food=\u0026#39;spam\u0026#39;, adjective=\u0026#39;absolutely horrible\u0026#39;)) This spam is absolutely horrible. 关键词参数和位置参数可以组合使用，例如：\n1 2 3 print(\u0026#39;The story of {0}, {1}, and {other}.\u0026#39;.format(\u0026#39;Bill\u0026#39;, \u0026#39;Manfred\u0026#39;, other=\u0026#39;Georg\u0026#39;)) The story of Bill, Manfred, and Georg. 按名称引入变量进行格式化。可以通过字典传递，使用[]进行访问 例如：\n1 2 3 4 table = {\u0026#39;Sjoerd\u0026#39;: 4127, \u0026#39;Jack\u0026#39;: 4098, \u0026#39;Dcab\u0026#39;: 8637678} print(\u0026#39;Jack: {0[Jack]:d}; Sjoerd: {0[Sjoerd]:d}; \u0026#39; \u0026#39;Dcab: {0[Dcab]:d}\u0026#39;.format(table)) Jack: 4098; Sjoerd: 4127; Dcab: 8637678 详解：此处的{0[Jack]:d}中参数的含义 0表示第零个参数，即table这个字典，Jack为关键字参数不必多说，：d表示demical，用十进制表示，绝非double 除此之外，此处的table还可以通过**作为参数，此时就不需要通过[]来访问了 例如\n1 print(\u0026#39;Jack: {Jack:d}; Sjoerd: {Sjoerd:d}; Dcab: {Dcab:d}\u0026#39;.format(**table)) 7.1.3.手动格式化字符串 蠢蠢的，自己看\n7.1.4.旧式字符串格式化方法 %用于字符串插值，感觉也蠢蠢的\n1 2 3 import math print(\u0026#39;The value of pi is approximately %5.3f.\u0026#39; % math.pi) The value of pi is approximately 3.142. 7.2.读写文件 open（）的应用： 返回值：一个file object，参数open（filename,mode,encoding = none） 各个参数的意义： filename：文件名字符串 mode：描述对文件的操作，包含以下几种值： r：read only，表示只读\tw：表示只写\ta：表示打开文件并追加内容，将写入的数据写入文件末尾\tr+：进行读写\t默认值为r encoding:选择的编码，一般默认UTF-8；在模式后面加b，可以使用binary mode打开文件，二级制数据以bytes对象形式进行读写的，该情况不允许指定encoding 而默认情况是以text mode打开 读取文件，会把平台特定的行终结符统一转化为\\n，而写入时，又会转化为平台对应的终结符 但是该操作会破坏二级制文件中的数据，这类数据只能通过二级制模式读写\nwith关键字：优点：字句结束后，文件会正确关闭，且代码更简洁\n1 2 with open(\u0026#39;workfile\u0026#39;, encoding=\u0026#34;utf-8\u0026#34;) as f: read_data = f.read() 没有with关键字，应通过f.close()关闭文件，释放资源， 若未使用close()关闭文件或者with关键字，可能导致写入的参数未写入磁盘\n7.2.1.文件对象的方法 前提：已创建f文件对象 f.read(size)：用于读取文件内容 返回值：字符串（文本模式），字节串（二进制模式） size：可选。默认值或复数：读取整个文件内容；其他值：读取size个字符或者size个字节 到达文件末尾返回空字符串’ ‘ f.readline():从文件中读取单行字符串 保留换行符’\\n\u0026rsquo;,例如\n1 2 f.readline() \u0026#39;This is the first line of the file.\\n\u0026#39; 只有在文件不以换行符结尾才省略换行符 若需要读取多行，通过循环遍历整个文件对象：\n1 2 3 4 5 for line in f: print(line, end=\u0026#39;\u0026#39;) This is the first line of the file. Second line of the file 如果需要以列表形式读取，则可以用list(f)或者f.readline() f.write(string):把string内容写入文件并返回写入的字符数 写入其他类型时，要先做类型转化，转化为string或者bytes\nf.seek(offset,whence)改变文件对象的位置 通过参考点添加offset计算位置 参考点有whence指定：0：从文件开头；1：使用当前文件位置；2：使用文件末尾 默认值为0 可以和f.tell()搭配使用 在文本文件（模式字符串未使用 b 时打开的文件）中，只允许相对于文件开头搜索（使用 seek(0, 2) 搜索到文件末尾是个例外），唯一有效的 offset 值是能从 f.tell() 中返回的，或 0。其他 offset 值都会产生未定义的行为。\n7.2.2. 使用 json 保存结构化数据¶ 字符串可以很容易地写入文件或从文件中读取。 数字则更麻烦一些，因为 read() 方法只返回字符串，而字符串必须传给 int() 这样的函数，它接受 '123' 这样的字符串并返回其数值 123。 当你想要保存嵌套列表和字典等更复杂的数据类型时，手动执行解析和序列化操作将会变得非常复杂。\nPython 允许你使用流行的数据交换格式 JSON (JavaScript Object Notation)，而不是让用户持续编写和调试代码来将复杂的数据类型存入文件中。 标准库模块 json 可以接受带有层级结构的 Python 数据，并将其转换为字符串表示形式；这个过程称为 serializing。 根据字符串表示形式JSON重建数据则称为 deserializing。 在序列化和反序列化之间，用于代表对象的字符串可以存储在文件或数据库中，或者通过网络连接发送到远端主机。\n备注\nJSON 格式通常用于现代应用程序的数据交换。程序员早已对它耳熟能详，可谓是交互操作的不二之选。\n只需一行简单的代码即可查看某个对象的 JSON 字符串表现形式：\n1 2 3 4 import json x = [1, \u0026#39;simple\u0026#39;, \u0026#39;list\u0026#39;] json.dumps(x) \u0026#39;[1, \u0026#34;simple\u0026#34;, \u0026#34;list\u0026#34;]\u0026#39; dumps() 函数还有一个变体， dump() ，它只将对象序列化为 text file 。因此，如果 f 是 text file 对象，可以这样做：\n1 json.dump(x, f) 要再次解码对象，如果 f 是已打开、供读取的 binary file 或 text file 对象：\n1 x = json.load(f) 备注\nJSON 文件必须以 UTF-8 编码。当打开 JSON 文件作为一个 text file 用于读写时，使用 encoding=\u0026quot;utf-8\u0026quot; 。\n这种简单的序列化技术可以处理列表和字典，但在 JSON 中序列化任意类的实例，则需要付出额外努力。json 模块的参考包含对此的解释。\n","permalink":"https://tuanzi-wow.cn/python/python-07-io/","summary":"\u003ch2 id=\"71更加复杂的输出格式\"\u003e7.1.更加复杂的输出格式\u003c/h2\u003e\n\u003ch3 id=\"格式化字符串字面值\"\u003e格式化字符串字面值\u003c/h3\u003e\n\u003cp\u003e在字符串开头的引号/三引号之前加f/F\n特性：可以通过{}在字符之间输入引用的变量，或者字面值的Python表达式\n例子如下：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv class=\"chroma\"\u003e\n\u003ctable class=\"lntable\"\u003e\u003ctr\u003e\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"lnt\"\u003e1\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e2\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e3\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e4\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eyear = 2016\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eevent = \u0026#39;Referendum\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ef\u0026#39;Results of the {year} {event}\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u0026#39;Results of the 2016 Referendum\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003ch4 id=\"strformat\"\u003estr.format（）:\u003c/h4\u003e\n\u003cp\u003e特性：1.同上，但你需要提供详细的格式化指令和待格式化的信息\n例子：\u003c/p\u003e","title":"7. 输入和输出"},{"content":"8.1.语法错误 又称解析错误 报错信息包括：出错的行；显示指向检测到错误位置的小箭头；文件名\n8.2.异常 在执行时检测到的错误称为异常 报错信息包括：异常的类型；错误细节\n8.3.异常的处理 try语句的工作原理： 1.执行try子句 2.没有触发异常，跳过except子句 3.触发异常，跳过try子句剩下部分，并且将异常与except后关键字进行匹配“如果匹配，则执行except子句；如果为未匹配，则停止执行并输出一条错误信息\n特性： 一个 try 可以有多个普通 except 子句，对于一次抛出的普通异常，只有第一个匹配的 except 子句会执行。一个 except 子句中的类匹配的异常将是该类本身的实例或其所派生的类的实例（但反过来则不可以 \u0026mdash; 列出派生类的 except 子句 不会匹配其基类的实例） 人话：基类（父类）异常可以匹配子类异常，反之不可以 so，需要主要异常匹配的顺序 异常参数 except子句可能在异常后指定一个变量（通过as），被绑定到异常实例，该实例有一个args属性，内置异常类型定义了 __str__() 来打印所有参数而不必显式地访问 .args。\n异常之间的关系 BaseException 是所有异常的共同基类。它的一个子类，Exception，是所有非致命异常的基类。不是 Exception 的子类的异常通常不被处理，因为它们被用来指示程序应该终止。它们包括由 sys.exit() 引发的 SystemExit，以及当用户希望中断程序时引发的 KeyboardInterrupt。\n异常匹配的注意点： Exception 可以被用作通配符，捕获（几乎）一切。然而，好的做法是，尽可能具体地说明我们打算处理的异常类型，并允许任何意外的异常传播下去。\ntry\u0026hellip;except语句具有可选的else语句\n异常处理程序还会处理在try子句中调用的函数的异常\n8.4.触发异常 使用raise语句，强制触发指定类型的异常 唯一的参数是要触发的异常，这个参数必须是异常实例或者异常类，如果是异常类，会隐式调用无参数的构造函数来创建一个异常实例\n8.5.异常链 raise a from b语句来显式表明一个异常（a）是另一个异常（b）的直接后果：此处的b必须为实例或者none，而未none时，表示禁用自动异常链\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 def func(): raise ConnectionError try: func() except ConnectionError as exc: raise RuntimeError(\u0026#39;Failed to open database\u0026#39;) from exc Traceback (most recent call last): File \u0026#34;\u0026lt;stdin\u0026gt;\u0026#34;, line 2, in \u0026lt;module\u0026gt; func() ~~~~^^ File \u0026#34;\u0026lt;stdin\u0026gt;\u0026#34;, line 2, in func ConnectionError The above exception was the direct cause of the following exception: Traceback (most recent call last): File \u0026#34;\u0026lt;stdin\u0026gt;\u0026#34;, line 4, in \u0026lt;module\u0026gt; raise RuntimeError(\u0026#39;Failed to open database\u0026#39;) from exc RuntimeError: Failed to open database 8.6.用户自定义异常 程序可以通过创建新的异常类命名自己的异常 (Python 类的内容详见 类)。 不论是以直接还是间接的方式，异常都应从 Exception 类派生。\n异常类可以被定义成能做其他类所能做的任何事，但通常应当保持简单，它往往只提供一些属性，允许相应的异常处理程序提取有关错误的信息。\n大多数异常命名都以\u0026quot;Error\u0026quot;结尾，类似标准异常的命名。\n许多标准模块定义了自己的异常，以报告他们定义的函数中可能出现的错误。\n8.7.定义清理操作 try-finally语句：定义在所有情况下都要执行的操作，通常在异常处理中用于清理操作 一些复杂的触发异常场景，感觉没什么意思，自己看看吧\n如果执行 try 子句期间触发了某个异常，则某个 except 子句应处理该异常。如果该异常没有 except 子句处理，在 finally 子句执行后会被重新触发。 except 或 else 子句执行期间也会触发异常。同样，该异常会在 finally 子句执行之后被重新触发。 如果 finally 子句执行 break、 continue 或 return 语句，异常不重新引发。这可能会引起混淆，因此不鼓励使用。从 3.14 版开始，编译器会为它发出一个 SyntaxWarning (参见 PEP 765)。 如果执行 try 语句时遇到 break、continue 或 return 语句，则 finally 子句在执行 break、continue 或 return 语句之前执行。 如果一个 finally 子句包含一个 return 语句，返回的值将是来自 finally 子句的 return 语句，而不是来自 try 子句的 return 语句。 这可能会引起混淆，因此不提倡使用。 从 3.14 版开始，编译器会为它发出一个 SyntaxWarning (参见 PEP 765)。 通常用于释放外部资源的逻辑\n8.8.预定义的清理操作（没啥意思，自己看） 某些对象定义了不需要该对象时要执行的标准清理操作。无论使用该对象的操作是否成功，都会执行清理操作。比如，下例要打开一个文件，并输出文件内容：\n1 2 for line in open(\u0026#34;myfile.txt\u0026#34;): print(line, end=\u0026#34;\u0026#34;) 这个代码的问题在于，执行完代码后，文件在一段不确定的时间内处于打开状态。在简单脚本中这没有问题，但对于较大的应用程序来说可能会出问题。with 语句支持以及时、正确的清理的方式使用文件对象：\n1 2 3 with open(\u0026#34;myfile.txt\u0026#34;) as f: for line in f: print(line, end=\u0026#34;\u0026#34;) 语句执行完毕后，即使在处理行时遇到问题，都会关闭文件 f。和文件一样，支持预定义清理操作的对象会在文档中指出这一点。\n8.9.引发和处理多个不相关的异常 场景：继续执行收集多个错误而不是引发第一个异常 方法一：内置的 ExceptionGroup 打包了一个异常实例的列表，这样它们就可以一起被引发。它本身就是一个异常，所以它可以像其他异常一样被捕获。 方法二：使用except替代except，有选择地处理异常，注意此处地except子句就不再是只能执行一个了\n例子：\n1 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 def f(): raise ExceptionGroup( \u0026#34;group1\u0026#34;, [ OSError(1), SystemError(2), ExceptionGroup( \u0026#34;group2\u0026#34;, [ OSError(3), RecursionError(4) ] ) ] ) try: f() except* OSError as e: print(\u0026#34;There were OSErrors\u0026#34;) except* SystemError as e: print(\u0026#34;There were SystemErrors\u0026#34;) There were OSErrors There were SystemErrors + Exception Group Traceback (most recent call last): | File \u0026#34;\u0026lt;stdin\u0026gt;\u0026#34;, line 2, in \u0026lt;module\u0026gt; | f() | ~^^ | File \u0026#34;\u0026lt;stdin\u0026gt;\u0026#34;, line 2, in f | raise ExceptionGroup( | ...\u0026lt;12 lines\u0026gt;... | ) | ExceptionGroup: group1 (1 sub-exception) +-+---------------- 1 ---------------- | ExceptionGroup: group2 (1 sub-exception) +-+---------------- 1 ---------------- | RecursionError: 4 +------------------------------------ 注意，嵌套在一个异常组中的异常必须是实例，而不是类型。这是因为在实践中，这些异常通常是那些已经被程序提出并捕获的异常\n8.10.用注释细化异常情况 add_note(note)用于在异常被捕获之后添加信息到异常地注释列表中 例如：\n1 2 3 4 5 6 7 8 9 10 11 12 13 try: raise TypeError(\u0026#39;bad type\u0026#39;) except Exception as e: e.add_note(\u0026#39;Add some information\u0026#39;) e.add_note(\u0026#39;Add some more information\u0026#39;) raise Traceback (most recent call last): File \u0026#34;\u0026lt;stdin\u0026gt;\u0026#34;, line 2, in \u0026lt;module\u0026gt; raise TypeError(\u0026#39;bad type\u0026#39;) TypeError: bad type Add some information Add some more information 应用于异常组中，例如：\n1 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 def f(): raise OSError(\u0026#39;operation failed\u0026#39;) excs = [] for i in range(3): try: f() except Exception as e: e.add_note(f\u0026#39;Happened in Iteration {i+1}\u0026#39;) excs.append(e) raise ExceptionGroup(\u0026#39;We have some problems\u0026#39;, excs) + Exception Group Traceback (most recent call last): | File \u0026#34;\u0026lt;stdin\u0026gt;\u0026#34;, line 1, in \u0026lt;module\u0026gt; | raise ExceptionGroup(\u0026#39;We have some problems\u0026#39;, excs) | ExceptionGroup: We have some problems (3 sub-exceptions) +-+---------------- 1 ---------------- | Traceback (most recent call last): | File \u0026#34;\u0026lt;stdin\u0026gt;\u0026#34;, line 3, in \u0026lt;module\u0026gt; | f() | ~^^ | File \u0026#34;\u0026lt;stdin\u0026gt;\u0026#34;, line 2, in f | raise OSError(\u0026#39;operation failed\u0026#39;) | OSError: operation failed | Happened in Iteration 1 +---------------- 2 ---------------- | Traceback (most recent call last): | File \u0026#34;\u0026lt;stdin\u0026gt;\u0026#34;, line 3, in \u0026lt;module\u0026gt; | f() | ~^^ | File \u0026#34;\u0026lt;stdin\u0026gt;\u0026#34;, line 2, in f | raise OSError(\u0026#39;operation failed\u0026#39;) | OSError: operation failed | Happened in Iteration 2 +---------------- 3 ---------------- | Traceback (most recent call last): | File \u0026#34;\u0026lt;stdin\u0026gt;\u0026#34;, line 3, in \u0026lt;module\u0026gt; | f() | ~^^ | File \u0026#34;\u0026lt;stdin\u0026gt;\u0026#34;, line 2, in f | raise OSError(\u0026#39;operation failed\u0026#39;) | OSError: operation failed | Happened in Iteration 3 +------------------------------------ ","permalink":"https://tuanzi-wow.cn/python/python-08-errors/","summary":"\u003ch2 id=\"81语法错误\"\u003e8.1.语法错误\u003c/h2\u003e\n\u003cp\u003e又称解析错误\n报错信息包括：出错的行；显示指向检测到错误位置的小箭头；文件名\u003c/p\u003e\n\u003ch2 id=\"82异常\"\u003e8.2.异常\u003c/h2\u003e\n\u003cp\u003e在执行时检测到的错误称为异常\n报错信息包括：异常的类型；错误细节\u003c/p\u003e\n\u003ch2 id=\"83异常的处理\"\u003e8.3.异常的处理\u003c/h2\u003e\n\u003cp\u003etry语句的\u003cstrong\u003e工作原理\u003c/strong\u003e：\n1.执行try子句\n2.没有触发异常，跳过except子句\n3.触发异常，跳过try子句剩下部分，并且将异常与except后关键字进行匹配“如果匹配，则执行except子句；如果为未匹配，则停止执行并输出一条错误信息\u003c/p\u003e","title":"8. 错误和异常"},{"content":"前言： 提供了把数据和功能绑定在一起的方法 支持面向对象中的多种特性： 基础机制支持多个基类;派生类能够覆盖基类的方法；类的方法能够调用基类中的同名方法 此外，还具有Python的动态特性，运行时创建，创建后可修改\n9.1.名称和对象 别名：对象之间相互独立，多个名称（甚至是多个作用域内的多个名称）可以绑定到同一对象。这在其他语言中通常被称为别名，这种现象主要是有益于可变对象，比如列表，，字典等等\n9.2.Python的作用域和命名空间 命名空间：namespace 名称到对象的映射 实现：大多数靠Python字典实现 不同命名空间中的名称之间绝对没有关系\n属性：在点之后的名称是属性（好奇怪的定义） 模块属性和模块中定义的全局名称之间存在直接的映射：它们共享相同的命名空间 属性可以是只读或者只写的，后一种情况可以对属性进行赋值，也可以通过del来进行删除\n命名空间的创建时间和生命周期（感觉有点复杂） 命名空间是在不同时刻创建的，且拥有不同的生命周期。内置名称的命名空间是在 Python 解释器启动时创建的，永远不会被删除。模块的全局命名空间在读取模块定义时创建；通常，模块的命名空间也会持续到解释器退出。从脚本文件读取或交互式读取的，由解释器顶层调用执行的语句是 __main__ 模块调用的一部分，也拥有自己的全局命名空间。内置名称实际上也在模块里，即 builtins 。\n函数的局部命名空间在函数被调用时被创建，并在函数返回或抛出未在函数内被处理的异常时，被删除。（实际上，用“遗忘”来描述实际发生的情况会更好一些。）当然，每次递归调用都有自己的局部命名空间。\n一个命名空间的 作用域 是 Python 代码中的一段文本区域，从这个区域可直接访问该命名空间。“可直接访问”的意思是，该文本区域内的名称在被非限定引用时，查找名称的范围，是包括该命名空间在内的。\n作用域虽然是被静态确定的，但会被动态使用。执行期间的任何时刻，都会有 3 或 4 个“命名空间可直接访问”的嵌套作用域：\n最内层作用域，包含局部名称，并首先在其中进行搜索 那些外层闭包函数的作用域，包含“非局部、非全局”的名称，从最靠内层的那个作用域开始，逐层向外搜索。 倒数第二层作用域，包含当前模块的全局名称 最外层（最后搜索）的作用域，是内置名称的命名空间 如果一个名称被声明为全局，则所有引用和赋值都将直接指向“倒数第二层作用域”，即包含模块的全局名称的作用域。 要重新绑定在最内层作用域以外找到的变量，可以使用 nonlocal 语句；如果未使用 nonlocal 声明，这些变量将为只读（尝试写入这样的变量将在最内层作用域中创建一个 新的 局部变量，而使得同名的外部变量保持不变）。\n通常，当前局部作用域将（按字面文本）引用当前函数的局部名称。在函数之外，局部作用域引用与全局作用域一致的命名空间：模块的命名空间。 类定义在局部命名空间内再放置另一个命名空间。\n划重点，作用域是按字面文本确定的：模块内定义的函数的全局作用域就是该模块的命名空间，无论该函数从什么地方或以什么别名被调用。另一方面，实际的名称搜索是在运行时动态完成的。但是，Python 正在朝着“编译时静态名称解析”的方向发展，因此不要过于依赖动态名称解析！（局部变量已经是被静态确定了。）\nPython 有一个特殊规定。如果不存在生效的 global 或 nonlocal 语句，则对名称的赋值总是会进入最内层作用域。赋值不会复制数据，只是将名称绑定到对象。删除也是如此：语句 del x 从局部作用域引用的命名空间中移除对 x 的绑定。所有引入新名称的操作都是使用局部作用域：尤其是 import 语句和函数定义会在局部作用域中绑定模块或函数名称。\nglobal 语句用于表明特定变量在全局作用域里，并应在全局作用域中重新绑定；nonlocal 语句表明特定变量在外层作用域中，并应在外层作用域中重新绑定。\n9.2.1.作用域和命名空间 一个经典的例子便于理解global和nonlocal对于变量绑定的影响\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 def scope_test(): def do_local(): spam = \u0026#34;local spam\u0026#34; def do_nonlocal(): nonlocal spam spam = \u0026#34;nonlocal spam\u0026#34; def do_global(): global spam spam = \u0026#34;global spam\u0026#34; spam = \u0026#34;test spam\u0026#34; do_local() print(\u0026#34;After local assignment:\u0026#34;, spam) do_nonlocal() print(\u0026#34;After nonlocal assignment:\u0026#34;, spam) do_global() print(\u0026#34;After global assignment:\u0026#34;, spam) scope_test() print(\u0026#34;In global scope:\u0026#34;, spam) 示例代码的输出是：\n1 2 3 4 After local assignment: test spam After nonlocal assignment: nonlocal spam After global assignment: nonlocal spam In global scope: global spam 注意，局部 赋值（这是默认状态）不会改变 scope_test 对 spam 的绑定。 nonlocal 赋值会改变 scope_test 对 spam 的绑定，而 global 赋值会改变模块层级的绑定。\n而且，global 赋值前没有 spam 的绑定。\n本人的理解：经验证是正确的，但是表述没有很准确，即函数名啥的写的比较粗略等等 首先 spam = \u0026ldquo;test spam\u0026quot;创建在scope_test作用域的spam，然后dolocal创建属于dolocal的spam然后do_nonlocal更新属于它外围u函数也就是socpetest的spam，然后doglobal创建全局的spam，在scope——test的每次输出都是输出属于scopetest的spam，但是最后一句 scope_test() print(\u0026ldquo;In global scope:\u0026rdquo;, spam) 会输出属于global的spam 此处要理解的关键就是各个作用域中的spam和 nonlocal与global对于绑定造成的影响 nonlocal是在外围函数中创建或者更新，比如此处的scope_test就是do_local等三个函数的外围函数 global则是在全局做更新或创建\n9.3.初探类 9.3.1.类的定义 class ClassName:\n9.3.2 Class对象 支持两种操作：\n1.属性引用 属性引用 语法：obj.name ____doc____为文档字符串属性\n2.实例化 当一个类定义了 __init__() 方法时，类的实例化会自动为新创建的类实例唤起 __init__()。 因此在这个例子中，可以通过以下语句获得一个已初始化的新实例: 当然，__init__() 方法还有一些参数用于实现更高的灵活性。 在这种情况下，提供给类实例化运算符的参数 将被传递给 __init__()。 例如\n9.3.3.实例对象 唯一操作为属性引用，引用的对象包括两个方面：函数和数据属性\n9.3.4.方法对象 方法对象可被储存卡里 例如：xf = x.f，print（xf） 方法的特殊之处就在于实例对象会作为函数的第一个参数被传入\n9.3.5.实例对象和类 正确设计实例变量和类变量\n一般来说，实例变量用于每个实例的唯一数据，而类变量用于类的所有实例共享的属性和方法:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Dog: kind = \u0026#39;canine\u0026#39; # 类变量被所有实例所共享 def __init__(self, name): self.name = name # 实例变量为每个实例所独有 \u0026gt;\u0026gt;\u0026gt; d = Dog(\u0026#39;Fido\u0026#39;) \u0026gt;\u0026gt;\u0026gt; e = Dog(\u0026#39;Buddy\u0026#39;) \u0026gt;\u0026gt;\u0026gt; d.kind # 被所有的 Dog 实例所共享 \u0026#39;canine\u0026#39; \u0026gt;\u0026gt;\u0026gt; e.kind # 被所有的 Dog 实例所共享 \u0026#39;canine\u0026#39; \u0026gt;\u0026gt;\u0026gt; d.name # 为 d 所独有 \u0026#39;Fido\u0026#39; \u0026gt;\u0026gt;\u0026gt; e.name # 为 e 所独有 \u0026#39;Buddy\u0026#39; 正如在 名称和对象 中所讨论的，共享数据可能在涉及 mutable 对象如列表和字典时导致令人惊讶的结果。 例如，以下代码中的 tricks 列表不应被用作类变量因为所有 Dog 实例将共享一个单独的列表:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Dog: tricks = [] # 类变量的错误用法 def __init__(self, name): self.name = name def add_trick(self, trick): self.tricks.append(trick) \u0026gt;\u0026gt;\u0026gt; d = Dog(\u0026#39;Fido\u0026#39;) \u0026gt;\u0026gt;\u0026gt; e = Dog(\u0026#39;Buddy\u0026#39;) \u0026gt;\u0026gt;\u0026gt; d.add_trick(\u0026#39;roll over\u0026#39;) \u0026gt;\u0026gt;\u0026gt; e.add_trick(\u0026#39;play dead\u0026#39;) \u0026gt;\u0026gt;\u0026gt; d.tricks # 非预期地被所有的 Dog 实例所共享 [\u0026#39;roll over\u0026#39;, \u0026#39;play dead\u0026#39;] 正确的类设计应该使用实例变量:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Dog: def __init__(self, name): self.name = name self.tricks = [] # 为每个 Dog 实例新建一个空列表 def add_trick(self, trick): self.tricks.append(trick) \u0026gt;\u0026gt;\u0026gt; d = Dog(\u0026#39;Fido\u0026#39;) \u0026gt;\u0026gt;\u0026gt; e = Dog(\u0026#39;Buddy\u0026#39;) \u0026gt;\u0026gt;\u0026gt; d.add_trick(\u0026#39;roll over\u0026#39;) \u0026gt;\u0026gt;\u0026gt; e.add_trick(\u0026#39;play dead\u0026#39;) \u0026gt;\u0026gt;\u0026gt; d.tricks [\u0026#39;roll over\u0026#39;] \u0026gt;\u0026gt;\u0026gt; e.tricks [\u0026#39;play dead\u0026#39;] 9.4.补充说明 1.同样的属性名称同时出现在类对象和实例对象时。优先选择实例对象\n1 2 3 4 5 6 7 8 9 10 11 12 class Warehouse: purpose = \u0026#39;storage\u0026#39; region = \u0026#39;west\u0026#39; w1 = Warehouse() print(w1.purpose, w1.region) storage west //相当与给w2创建了一个实例属性region，要与属于warehouse的region属性区别开 w2 = Warehouse() w2.region = \u0026#39;east\u0026#39; print(w2.purpose, w2.region) storage east 方法的第一个参数常常被命名为 self。 这也不过就是一个约定: self 这一名称在 Python 中绝对没有特殊含义。 但是要注意，不遵循此约定会使得你的代码对其他 Python 程序员来说缺乏可读性，而且也可以想像一个 类浏览器 程序的编写可能会依赖于这样的约定。\n9.5. 继承 派生类的语法：class DeriveClassName(BaseClassName): 若基类位于另一个module， 语法为：class DerivedClassName(modname.BaseClassName):\n关于重写： Python 中所有的方法实际上都是 virtual 方法 如果想要调用基类的方法而非派生类的方法：BaseClassName.methodname(self, arguments)\n两个有关继承机制的内置函数： isinstancel():isinstance(obj,int) 当obj为int或者int的子类时为true； issubclass(a,b):判断a是不是b的子类\n9.5.1.多重继承 语法：class DerivedClassName(Base1, Base2, Base3): 多个基类访问顺序：有点复杂，自己看对于多数目的来说，在最简单的情况下，你可以认为搜索从父类所继承属性的操作是深度优先、从左到右的，当层次结构存在重叠时不会在同一个类中搜索两次。 因此，如果某个属性在 DerivedClassName 中找不到，就会在 Base1 中搜索它，然后（递归地）在 Base1 的基类中搜索，如果在那里也找不到，就将在 Base2 中搜索，依此类推。\n真实情况比这个更复杂一些；方法解析顺序会动态改变以支持对 super() 的协同调用。 这种方式在某些其他多重继承型语言中被称为后续方法调用，它比单继承型语言中的 super 调用更强大。\n动态调整顺序是有必要的，因为所有多重继承的情况都会显示出一个或更多的菱形关联（即至少有一个上级类可通过多条路径被最底层的类所访问）。 例如，所有类都是继承自 object，因此任何多重继承的情况都提供了一条以上的路径可以通向 object。 为了确保基类不会被访问一次以上，动态算法会用一种特殊方式将搜索顺序线性化，保留每个类所指定的从左至右的顺序，只调用每个上级类一次，并且保持单调性（即一个类可以被子类化而不影响其父类的优先顺序）。 总而言之，这些特性使得设计具有多重继承的可靠且可扩展的类成为可能。 要了解更多细节，请参阅 Python 2.3 方法解析顺序。\n9.6.私有变量 意识到：仅限从一个对象内部访问的“私有”实例变量在 Python 中并不存在 约定为：带有一个下划线的名称 (例如 _spam) 应该被当作是 API 的非公有部分 名称改写：任何形式为 __spam 的标识符（至少带有两个前缀下划线，至多一个后缀下划线）的文本将被替换为 _classname__spam，其中 classname 为去除了前缀下划线的当前类名称。\n9.7.杂项说明 使用datalclasses：将一些带名称的数据项捆绑在一起 from dataclasses import dataclass\n1 2 3 4 5 @dataclass class Employee: name: str dept: str salary: int 9.8.迭代器 for语句： 原理：在容器对象调用iter（）生成一个带有\u0026ndash;next\u0026ndash;（）方法的迭代器对象，准一访问到末尾引发StopIteration异常来中止 通过next（）函数调用\u0026ndash;next\u0026ndash;（）\n9.9.生成器 一个创建迭代器的工具,返回数据时会使用yield语句\nyield与return的区别:yield不会终结函数而是暂停函数,这正好符合生成器的需要 几个特性: 但生成器的写法更为紧凑，因为它会自动创建 __iter__() 和 __next__() 方法。 另一个关键特性在于局部变量和执行状态会在每次调用之间自动保存。 当生成器终结时，它们还会自动引发 StopIteration。\n9.10.生成器表达式(懒得解释了自己看) 某些简单的生成器可以写成简洁的表达式代码，所用语法类似列表推导式，但外层为圆括号而非方括号。 这种表达式被设计用于生成器将立即被外层函数所使用的情况。 生成器表达式相比完整的生成器更紧凑但较不灵活，相比等效的列表推导式则更为节省内存。\n示例:\nCopy\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 sum(i*i for i in range(10)) # sum of squares 285 xvec = [10, 20, 30] yvec = [7, 5, 3] sum(x*y for x,y in zip(xvec, yvec)) # dot product 260 unique_words = set(word for line in page for word in line.split()) valedictorian = max((student.gpa, student.name) for student in graduates) data = \u0026#39;golf\u0026#39; list(data[i] for i in range(len(data)-1, -1, -1)) [\u0026#39;f\u0026#39;, \u0026#39;l\u0026#39;, \u0026#39;o\u0026#39;, \u0026#39;g\u0026#39;] ","permalink":"https://tuanzi-wow.cn/python/python-09-classes/","summary":"\u003cp\u003e前言：\n提供了把数据和功能绑定在一起的方法\n支持面向对象中的多种特性：\n基础机制支持多个基类;派生类能够覆盖基类的方法；类的方法能够调用基类中的同名方法\n此外，还具有Python的动态特性，运行时创建，创建后可修改\u003c/p\u003e","title":"9. 类"},{"content":"10.1.操作系统接口 import os 一定要使用import os而非from os import * 原因：防止内建的open（）函数被os.open()隐式替换掉 内置的dir（）和help（）函数可用作交互式的辅助工具\n1 2 3 4 dir(os) \u0026lt;返回由模块的所有函数组成的列表\u0026gt; help(os) \u0026lt;返回根据模块文档字符串创建的详细说明页面\u0026gt; 对于日常文件和目录管理任务，shutil模块提供了易用的高层级接口\n10.2.文件通配符 glob模块提供了一个在目录中使用通配符搜索创建文件列表的函数\n示例如下:\n1 2 3 import glob glob.glob(\u0026#39;*.py\u0026#39;) [\u0026#39;primes.py\u0026#39;, \u0026#39;random.py\u0026#39;, \u0026#39;quote.py\u0026#39;] 10.3.命令行参数 参数是通过列表形式储存在sys模块的argv属性中 import sys来引入 argparse模块argparse 模块提供了一种更复杂的机制来处理命令行参数。 以下脚本可提取一个或多个文件名并可选择要显示的行数:（要用自己看）\n1 2 3 4 5 6 7 8 9 import argparse parser = argparse.ArgumentParser( prog=\u0026#39;top\u0026#39;, description=\u0026#39;Show top lines from each file\u0026#39;) parser.add_argument(\u0026#39;filenames\u0026#39;, nargs=\u0026#39;+\u0026#39;) parser.add_argument(\u0026#39;-l\u0026#39;, \u0026#39;--lines\u0026#39;, type=int, default=10) args = parser.parse_args() print(args) 当通过 python top.py --lines=5 alpha.txt beta.txt 在命令行运行时，该脚本会将 args.lines 设为 5 并将 args.filenames 设为 ['alpha.txt', 'beta.txt']。\n10.4.错误输出重定向和程序终止 sys模块的stdin，stdout，stderr属性，其中stderr在发出警告和错误信息很有用 终止脚本：sys.exit()\n10.5.字符串模式匹配 re模块为高级表达式提供正则表达式工具\n1 2 3 4 5 import re re.findall(r\u0026#39;\\bf[a-z]*\u0026#39;, \u0026#39;which foot or hand fell fastest\u0026#39;) [\u0026#39;foot\u0026#39;, \u0026#39;fell\u0026#39;, \u0026#39;fastest\u0026#39;] re.sub(r\u0026#39;(\\b[a-z]+) \\1\u0026#39;, r\u0026#39;\\1\u0026#39;, \u0026#39;cat in the the hat\u0026#39;) \u0026#39;cat in the hat\u0026#39; 1 2 \u0026#39;tea for too\u0026#39;.replace(\u0026#39;too\u0026#39;, \u0026#39;two\u0026#39;) \u0026#39;tea for two\u0026#39; 正则表达式是的，yield 和普通函数中的 return 最大的不同在于：yield 不会让函数彻底结束，而是会让函数“暂停”，以后还能从暂停的位置继续执行。\n这就是它最特别的地方。\n正则 含义 r'' Python 原始字符串 \\b 单词边界 [a-z] 任意一个小写字母 * 0 次或多次 + 1 次或多次 () 捕获组 \\1 引用第一个捕获组 findall() 找出所有匹配内容 sub() 查找并替换 10.6.数字 math模块：众多数学方法 random：随机选择工具 statistics：计算数值数据的基本统计属性：例如均值，中位数，方差等等\n10.7.互联网访问 挺多的，最简单的两个 urllib.request 用于从URL检索数据，以及 smtplib 用于发送邮件\n10.8.日期和时间 datatime模块\n10.9.数据压缩 常见的数据存档和压缩格式由模块直接支持，包括：zlib, gzip, bz2, lzma, zipfile 和 tarfile\n10.10.性能测试 10.10. 性能测量 一些 Python 用户对了解同一问题的不同方法的相对性能产生了浓厚的兴趣。Python 提供了一种可以立即回答这些问题的测量工具。\n例如，元组封包和拆包功能相比传统的交换参数可能更具吸引力。timeit 模块可以快速演示在运行效率方面一定的优势:\n1 2 3 4 5 from timeit import Timer Timer(\u0026#39;t=a; a=b; b=t\u0026#39;, \u0026#39;a=1; b=2\u0026#39;).timeit() 0.57535828626024577 Timer(\u0026#39;a,b = b,a\u0026#39;, \u0026#39;a=1; b=2\u0026#39;).timeit() 0.54962537085770791 与 timeit 的精细粒度级别相反， profile 和 pstats 模块提供了用于在较大的代码块中识别时间关键部分的工具。\n10.11.质量控制（写测试） 这个感觉会有点用\n开发高质量软件的一种方法是在开发过程中为每个函数编写测试，并在开发过程中经常运行这些测试。\ndoctest 模块提供了一个工具，用于扫描模块并验证程序文档字符串中嵌入的测试。测试构造就像将典型调用及其结果剪切并粘贴到文档字符串一样简单。这通过向用户提供示例来改进文档，并且它允许 doctest 模块确保代码与文档保持一致:\n1 2 3 4 5 6 7 8 9 10 def average(values): \u0026#34;\u0026#34;\u0026#34;计算数字列表的算术平均值 \u0026gt;\u0026gt;\u0026gt; print(average([20, 30, 70])) 40.0 \u0026#34;\u0026#34;\u0026#34; return sum(values) / len(values) import doctest doctest.testmod() # 自动验证嵌入式测试 unittest 模块不像 doctest 模块那样易于使用，但它允许在一个单独的文件中维护更全面的测试集:\n1 2 3 4 5 6 7 8 9 10 11 12 13 import unittest class TestStatisticalFunctions(unittest.TestCase): def test_average(self): self.assertEqual(average([20, 30, 70]), 40.0) self.assertEqual(round(average([1, 5, 7]), 1), 4.3) with self.assertRaises(ZeroDivisionError): average([]) with self.assertRaises(TypeError): average(20, 30, 70) unittest.main() # 从命令行调用时会执行所有测试 10.12.内置电池 Python 有\u0026quot;自带电池\u0026quot;的理念。通过其包的复杂和强大功能可以最好地看到这一点。例如:\nxmlrpc.client 和 xmlrpc.server 模块使得实现远程过程调用变成了小菜一碟。 尽管存在于模块名称中，但用户不需要直接了解或处理 XML。 email 包是一个用于管理电子邮件消息的库，包括 MIME 和其他基于 RFC 5322 的消息文档。 不同于实际发送和接收消息的 smtplib 和 poplib，email 包提供用于构建或解码复杂消息结构（包括附件）以及实现互联网编码格式和标头协议的完整工具集。 json 包为解析这种流行的数据交换格式提供了强大的支持。 csv 模块支持以逗号分隔值格式直接读取和写入文件，这种格式通常为数据库和电子表格所支持。 XML 处理由 xml.etree.ElementTree ， xml.dom 和 xml.sax 包支持。这些模块和软件包共同大大简化了 Python 应用程序和其他工具之间的数据交换。 sqlite3 模块是 SQLite 数据库库的包装器，提供了一个可以使用稍微非标准的 SQL 语法更新和访问的持久数据库。 国际化由许多模块支持，包括 gettext ， locale ，以及 codecs 包。 11. 标准库概览 · 第二部分 11.1.输出的格式化 reprlib 模块提供了一个定制化版本的 repr() 函数，用于缩略显示大型或深层嵌套的容器对象 pprint 模块提供了更加复杂的打印控制，其输出的内置对象和用户自定义对象能够被解释器直接读取。当输出结果过长而需要折行时，“美化输出机制”会添加换行符和缩进，以更清楚地展示数据结构: textwrap 模块能够格式化文本段落，以适应给定的屏幕宽度: locale 模块处理与特定地域文化相关的数据格式。locale 模块的 format 函数包含一个 grouping 属性，可直接将数字格式化为带有组分隔符的样式:\n11.2.模块 string 模块包含一个通用的 Template 类，具有适用于最终用户的简化语法。它允许用户在不更改应用逻辑的情况下定制自己的应用。\n上述格式化操作是通过占位符实现的，占位符由 $ 加上合法的 Python 标识符（只能包含字母、数字和下划线）构成。一旦使用花括号将占位符括起来，就可以在后面直接跟上更多的字母和数字而无需空格分割。$$ 将被转义成单个字符 $:\n1 2 3 4 from string import Template t = Template(\u0026#39;${village}folk send $$10 to $cause.\u0026#39;) t.substitute(village=\u0026#39;Nottingham\u0026#39;, cause=\u0026#39;the ditch fund\u0026#39;) \u0026#39;Nottinghamfolk send $10 to the ditch fund.\u0026#39; 如果在字典或关键字参数中未提供某个占位符的值，那么 substitute() 方法将抛出 KeyError。对于邮件合并类型的应用，用户提供的数据有可能是不完整的，此时使用 safe_substitute() 方法更加合适 —— 如果数据缺失，它会直接将占位符原样保留。\n11.3.操作二进制数据记录布局 struct 模块提供了 pack() 和 unpack() 函数，用于处理不定长度的二进制记录格式。\n11.4. 多线程（之前学JAVA有点学吐了） 线程是一种对于非顺序依赖的多个任务进行解耦的技术。多线程可以提高应用的响应效率，当接收用户输入的同时，保持其他任务在后台运行。一个有关的应用场景是，将 I/O 和计算运行在两个并行的线程中。\n以下代码展示了高阶的 threading 模块如何在后台运行任务，且不影响主程序的继续运行:\nCopy\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import threading, zipfile class AsyncZip(threading.Thread): def __init__(self, infile, outfile): super().__init__() self.infile = infile self.outfile = outfile def run(self): with zipfile.ZipFile(self.outfile, \u0026#39;w\u0026#39;, zipfile.ZIP_DEFLATED) as f: f.write(self.infile) print(\u0026#39;Finished background zip of:\u0026#39;, self.infile) background = AsyncZip(\u0026#39;mydata.txt\u0026#39;, \u0026#39;myarchive.zip\u0026#39;) background.start() print(\u0026#39;The main program continues to run in foreground.\u0026#39;) background.join() # 等待后台任务结束 print(\u0026#39;Main program waited until background was done.\u0026#39;) 多线程应用面临的主要挑战是，相互协调的多个线程之间需要共享数据或其他资源。为此，threading 模块提供了多个同步操作原语，包括线程锁、事件、条件变量和信号量。\n尽管这些工具非常强大，但微小的设计错误却可以导致一些难以复现的问题。因此，实现多任务协作的首选方法是将所有对资源的请求集中到一个线程中，然后使用 queue 模块向该线程供应来自其他线程的请求。 应用程序使用 Queue 对象进行线程间通信和协调，更易于设计，更易读，更可靠。\n11.5.日志记录 logging 模块提供功能齐全且灵活的日志记录系统,在最简单的情况下，日志消息被发送到文件或 sys.stderr\n1 2 3 4 5 6 import logging logging.debug(\u0026#39;Debugging information\u0026#39;) logging.info(\u0026#39;Informational message\u0026#39;) logging.warning(\u0026#39;Warning:config file %s not found\u0026#39;, \u0026#39;server.conf\u0026#39;) logging.error(\u0026#39;Error occurred\u0026#39;) logging.critical(\u0026#39;Critical error -- shutting down\u0026#39;) 11.6.弱引用（这个在JAVA的GC相关部分貌似也有） weakref 模块提供的工具可以不必创建引用就能跟踪对象。 当对象不再需要时，它将自动从一个弱引用表中被移除，并为弱引用对象触发一个回调。 典型应用包括对创建开销较大的对象进行缓存\n11.7.操作列表的工具（懒得看，要用的时候自然会查） 许多对于数据结构的需求可以通过内置列表类型来满足。 但是，有时也会需要具有不同效费比的替代实现。\narray 模块提供了一种 array 对象，它类似于列表，但只能存储类型一致的数据且存储密度更高。 下面的例子显示了一个由存储为双字节无符号整数的数字 (类型码 \u0026quot;H\u0026quot;) 组成的元组，而不是常规 Python int 对象列表所采用的每个条目 16 字节:\n1 2 3 4 5 6 from array import array a = array(\u0026#39;H\u0026#39;, [4000, 10, 700, 22222]) sum(a) 26932 a[1:3] array(\u0026#39;H\u0026#39;, [10, 700]) collections 模块提供了一种 deque 对象，它类似于列表，但从左端添加和弹出的速度较快而在中间查找的速度较慢。 此种对象适用于实现队列和广度优先树搜索:\n1 2 3 4 5 6 7 8 9 10 11 12 from collections import deque d = deque([\u0026#34;task1\u0026#34;, \u0026#34;task2\u0026#34;, \u0026#34;task3\u0026#34;]) d.append(\u0026#34;task4\u0026#34;) print(\u0026#34;Handling\u0026#34;, d.popleft()) Handling task1 unsearched = deque([starting_node]) def breadth_first_search(unsearched): node = unsearched.popleft() for m in gen_moves(node): if is_goal(m): return m unsearched.append(m) 在替代的列表实现以外，标准库也提供了其他工具，例如 bisect 模块具有用于操作有序列表的函数:\n1 2 3 4 5 import bisect scores = [(100, \u0026#39;perl\u0026#39;), (200, \u0026#39;tcl\u0026#39;), (400, \u0026#39;lua\u0026#39;), (500, \u0026#39;python\u0026#39;)] bisect.insort(scores, (300, \u0026#39;ruby\u0026#39;)) scores [(100, \u0026#39;perl\u0026#39;), (200, \u0026#39;tcl\u0026#39;), (300, \u0026#39;ruby\u0026#39;), (400, \u0026#39;lua\u0026#39;), (500, \u0026#39;python\u0026#39;)] heapq 模块提供了基于常规列表来实现堆的函数。 最小值的条目总是保持在位置零。 这对于需要重复访问最小元素而不希望运行完整列表排序的应用来说非常有用:\n1 2 3 4 5 6 from heapq import heapify, heappop, heappush data = [1, 3, 5, 7, 9, 2, 4, 6, 8, 0] heapify(data) # 将列表重新调整为堆顺序 heappush(data, -5) # 添加一个新条目 [heappop(data) for i in range(3)] # 获取三个最小的条目 [-5, 0, 1] 11.8.十进制浮点算法 decimal 模块提供了一种 Decimal 数据类型用于十进制浮点运算。 相比内置的 float 二进制浮点实现，该类特别适用于 这个算有点用\n财务应用和其他需要精确十进制表示的用途， 控制精度， 控制四舍五入以满足法律或监管要求， 跟踪有效小数位，或 用户期望结果与手工完成的计算相匹配的应用程序。 Decimal 表示的结果会保留尾部的零，并根据具有两个有效位的被乘数自动推出四个有效位。 Decimal 可以模拟手工运算来避免当二进制浮点数无法精确表示十进制数时会导致的问题。\n","permalink":"https://tuanzi-wow.cn/python/python-10-stdlib/","summary":"\u003ch2 id=\"101操作系统接口\"\u003e10.1.操作系统接口\u003c/h2\u003e\n\u003cp\u003eimport os\n一定要使用import os而非from os import *\n原因：防止内建的open（）函数被os.open()隐式替换掉\n内置的dir（）和help（）函数可用作交互式的辅助工具\u003c/p\u003e","title":"10. 标准库概览"},{"content":"12.1概述 使用不存在标准库的软件包和模块 需要特定的库\n解决方法1：创建一个虚拟环境（virtual environment):一个目录树，安装有特定版本的Python版本，以及许多其他包\n12.2.创建虚拟环境 用于创建虚拟环境的模块：venv 例如：python -m venv tutorial-env 虚拟环境的常见目录为.venv，通常会被隐藏 激活该虚拟环境的语句：tutorial-env\\Scripts\\activate 撤销激活一个虚拟环境 激活的虚拟环境并不会以任何方式改变PYTHONPATH；最佳实践实在bash中执行unset PYTHONPATH\n12.3.使用pip管理包 作用：安装升级，移除软件包 默认情况下pip或从Python Package Index安装软件包 完整的用法请查阅\n","permalink":"https://tuanzi-wow.cn/python/python-12-venv/","summary":"\u003ch2 id=\"121概述\"\u003e12.1概述\u003c/h2\u003e\n\u003cp\u003e使用不存在标准库的软件包和模块\n需要特定的库\u003c/p\u003e\n\u003cp\u003e解决方法1：创建一个虚拟环境（virtual environment):一个目录树，安装有特定版本的Python版本，以及许多其他包\u003c/p\u003e","title":"12. 虚拟环境和包"},{"content":"情景： 0.1永远无法用以2为基底的小数，他是一个无限循环小数 0.0001100110011001100110011001100110011001100110011\u0026hellip; 你只能得到一个近似值 比较有意思的一个点： 1/10 = 0.1 貌似是存储的精确值，但是实际上只是存储的最接近0.1的计算机可表示的二进制分数 于此同时，有许多额十进制数共享该最接近的二进制分数 比如： 0.1 、 0.10000000000000001 、 0.1000000000000000055511151231257827021181583404541015625 全都近似于 3602879701896397 / 2 ** 55 如果想要美观的输出，可以通过字符串格式化限定输出的位数\n还有一个有趣的现象 0.1 + 0.1 + 0.1 == 0.3为False 这个原理就是上述 但是捏 如果一定要进行不精确表达，可以使用math.isclose()\nBUT！ 正如那篇文章的结尾所言，“对此问题并无简单的答案。” 但是也不必过于担心浮点数的问题！ Python 浮点运算中的错误是从浮点运算硬件继承而来，而在大多数机器上每次浮点运算的误差不超过 2**53 分之一。 这对大多数任务来说都已足够，但你确实需要记住它并非十进制算术，且每次浮点运算都可能会导致新的舍入错误。\n而如果想要进行精确的十进制模块的计算，尝试使用decimal模块或者fractions模块 还有很多模块，但是我懒得赘述了 自己看看吧\n15.1.表示性错误 定义：指的是某些十进制小数无法以二进制精确表示这一事实造成的失误本小节将详细解释 \u0026ldquo;0.1\u0026rdquo; 的例子，并说明你可以怎样亲自对此类情况进行精确分析。 假定前提是已基本熟悉二进制浮点表示法。\n表示性错误 是指某些（其实是大多数）十进制小数无法以二进制（以 2 为基数的计数制）精确表示这一事实造成的错误。 这就是为什么 Python（或者 Perl、C、C++、Java、Fortran 以及许多其他语言）经常不会显示你所期待的精确十进制数值的主要原因。\n为什么会这样？ 1/10 是无法用二进制小数精确表示的。 至少从 2000 年起，几乎所有机器都使用 IEEE 754 二进制浮点运算标准，而几乎所有系统平台都将 Python 浮点数映射为 IEEE 754 binary64 \u0026ldquo;双精度\u0026rdquo; 值。 IEEE 754 binary64 值包含 53 位精度，因此在输入时计算机会尽量将 0.1 转换为以 J/2**N 形式所能表示的最接近的小数，其中 J 为恰好包含 53 比特位的整数。 重新将\n1 1 / 10 ~= J / (2**N) 写为\n1 J ~= 2**N / 10 并且由于 J 恰好有 53 位 (即 \u0026gt;= 2**52 但 \u0026lt; 2**53)，N 的最佳值为 56:\n1 2 2**52 \u0026lt;= 2**56 // 10 \u0026lt; 2**53 True 也就是说，56 是唯一能使 J 恰好有 53 位的 N 值。 这样 J 可能的最佳就是舍入之后的商:\n1 2 3 q, r = divmod(2**56, 10) r 6 由于余数超过 10 的一半，所以最佳近似值可通过向上舍入获得:\n1 2 q+1 7205759403792794 因此在 IEEE 754 双精度下可能达到的 1/10 的最佳近似值为:\n1 7205759403792794 / 2 ** 56 分子和分母都除以二则结果小数为:\n1 3602879701896397 / 2 ** 55 请注意由于我们做了向上舍入，这个结果实际上略大于 1/10；如果我们没有向上舍入，则商将会略小于 1/10。 但无论如何它都不会是 精确的 1/10！\n因此计算机永远不会 \u0026ldquo;看到\u0026rdquo; 1/10: 它实际看到的就是上面所给出的小数，即它能达到的最佳 IEEE 754 双精度近似值:\n1 2 0.1 * 2 ** 55 3602879701896397.0 如果我们将该小数乘以 10**55，我们可以看到该值输出 55 个十进制数位:\n1 2 3602879701896397 * 10 ** 55 // 2 ** 55 1000000000000000055511151231257827021181583404541015625 这意味着存储在计算机中的确切数字等于十进制数值 0.1000000000000000055511151231257827021181583404541015625。 许多语言（包括较旧版本的 Python）都不会显示这个完整的十进制数值，而是将结果舍入到 17 位有效数字:\n1 2 format(0.1, \u0026#39;.17f\u0026#39;) \u0026#39;0.10000000000000001\u0026#39; fractions 和 decimal 模块使得这样的计算更为容易:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 from decimal import Decimal from fractions import Fraction Fraction.from_float(0.1) Fraction(3602879701896397, 36028797018963968) (0.1).as_integer_ratio() (3602879701896397, 36028797018963968) Decimal.from_float(0.1) Decimal(\u0026#39;0.1000000000000000055511151231257827021181583404541015625\u0026#39;) format(Decimal.from_float(0.1), \u0026#39;.17\u0026#39;) \u0026#39;0.10000000000000001\u0026#39; 自己看看吧，讲得还是挺清楚的\n","permalink":"https://tuanzi-wow.cn/python/python-15-float/","summary":"\u003cp\u003e情景：\n0.1永远无法用以2为基底的小数，他是一个无限循环小数\n0.0001100110011001100110011001100110011001100110011\u0026hellip;\n你只能得到一个近似值\n比较有意思的一个点：\n1/10 = 0.1 貌似是存储的精确值，但是实际上只是存储的最接近0.1的计算机可表示的二进制分数\n于此同时，有许多额十进制数共享该最接近的二进制分数\n比如： \u003ccode\u003e0.1\u003c/code\u003e 、 \u003ccode\u003e0.10000000000000001\u003c/code\u003e 、 \u003ccode\u003e0.1000000000000000055511151231257827021181583404541015625\u003c/code\u003e 全都近似于 \u003ccode\u003e3602879701896397 / 2 ** 55\u003c/code\u003e\n如果想要美观的输出，可以通过字符串格式化限定输出的位数\u003c/p\u003e","title":"15. 浮点算术：问题和限制"}]