背景
帮忙点点star吧 https://github.com/Disdjj/prompt_in_code
前段时间, openai发布了o1, 体验一段时间之后, 虽然我认为在实际上没有基础模型的提升, 但是其自动产生COT, 主动思考解决问题的方案, 我觉得非常有趣, 在一段时间的研究之后, 我认为Code-Prompt能够模拟实现一部分o1的能力
和现在github上一些垃圾实现, 通过多轮对话实现不同, 通过Code-Prompt可以在一轮的请求中, 产生最后的结果.
当然了, 这并不是没有代价的, 是通过output-learning
来实现的, 所以最后的输出会非常的长
其实, 我并不知道该怎么称呼output-learning
, 简单来说这是我在实践中总结的一个现象, 那就是当LLM在输出时, 如果能够主动的在输出中产生一部分调度类的内容, 那么很大概率后续的输出就会产生更好的发散性/遵从/思考能力.
COT/TOT等等其实都是output-learning
的一部分, 那就是显式的暴露思考步骤
实现
我们可以先思考, 一个自动COT的Agent应该具有哪些能力呢?
-
分析问题
- 如果问题已经是一个基本问题, 无法继续划分, 则不再分析
- 问题涉及的领域
- 问题的表述
- 用几种不同的方式来复述这个问题
- 自动添加few-shot
-
思考解决步骤
- 拆解成N个子问题
- N个子问题重复步骤1, 将所有的子问题合并为一个问题列表
- 分析问题所归属的领域, 分析最适合的专家类型, 分析回答的最关键的List
-
开始解决问题列表
- load一个专家, load一个领域知识提示
- 回答问题Q, 回答为A
- 专家Check回答A, 为了回答Q, 基于A重新生成回答
-
将所有的Q和A, 组织成一个QAList
-
基于QA List, 回答原始问题
实现Prompt
# YOU ARE A PROCESS, EXECUTE THE FOLLOWING CODE!
# ONLY OUTPUT THE CODE RESULT!
# llm Package is yourself(LLM)'s ability
from llm import (
deepthink,
role_play,
expert,
judge,
summarize,
)
from llm.io import (
input,
output,
)
from llm.knowledge import (
load_knowledge_region,
)
from attr import dataclass
@dataclass
class Q:
query: str # an atom question, like "what is the meaning of life?"
knowledge: list[str] # list of str, like ["philosophy", "life"]
expert: str # like "philosopher"
steps: list[str] # list of str, steps to solve the question
@dataclass
class Analysis:
query: str
sub_questions: list[Analysis]
def gene_Q(query: str) -> Q:
"""
generate a Q object from a query, including knowledge, expert, check_list, examples
"""
knowledge = load_knowledge_region(query)
expert_name = expert(knowledge).name
steps = deepthink(f"generate a list of steps to solve the question 'f{query}'")
return Q(query=query, knowledge=knowledge, expert=expert_name, steps=steps)
def analyze(query: str) -> [Analysis]:
"""
analyze a query to a analysis, if the query is a atom question, return a analysis with is_atom_question=True, else return a analysis with
is_atom_question=False and a list of sub questions
"""
# analyze the query, generate a list of sub questions which are the most important parts of the query and the most related to the query
queries = deepthink(
f"ask more questions for solve 'f{query}', You should ask questions about all atomic definitions/concepts/spellings, just like using a beginner.",
min=1,
max=10)
return queries
def convert_to_Qs(analysis: Analysis) -> list[Q]:
"""
convert a analysis to a list of Q
"""
result = []
while analysis.sub_questions:
result.append(gene_Q(analysis.query))
for sub_analysis in analysis.sub_questions:
result.extend(convert_to_Qs(sub_analysis))
return result
def generate_answer(Q: Q) -> ([str], str):
"""
generate an answer for a Q, including the expert's answer, check_list, examples, steps
always follow steps to generate the answer
always check the answer by check_list
always learn from examples
always load skill from knowledge
"""
step_result = []
for step in Q.steps:
step_result.append(role_play(Q.expert, Q.query, Q.knowledge, step=step))
final_answer = summarize(step_result)
return step_result, final_answer
def o1(query: str):
result = {"raw_query": query}
# step 1: analyze the query
analysis = analyze(query)
result["analysis"] = analysis
# step 2: convert the analysis to a list of Q
q_list = convert_to_Qs(analysis)
result["Qs"] = q_list
# step 3: generate a list of answers
answers = []
for q in q_list:
step_result, q_answer = generate_answer(q)
answers.append(
{
"query": q.query,
"steps": step_result,
"answer": q_answer,
})
result["answers"] = answers
# step 4:
final_answer = summarize(result)
result["final_answer"] = final_answer
return result
# main function
if __name__ == '__main__':
query = input("what do you want to know?")
result = o1(query)
# result = analyze(query)
output(result, output_format="json", code_region=True) # output the result in json format but in a code region:```json ... ```
效果
Base On DeepSeek 温度: 0.7
我怎么解决 TWO SUM的编程问题呢?
{
"raw_query": "我怎么解决 TWO SUM的编程问题呢?",
"analysis": [
"什么是TWO SUM问题?",
"TWO SUM问题的输入是什么?",
"TWO SUM问题的输出是什么?",
"TWO SUM问题的目标是什么?",
"解决TWO SUM问题的常见算法有哪些?",
"如何实现TWO SUM问题的解决方案?",
"TWO SUM问题的复杂度是多少?"
],
"Qs": [
{
"query": "什么是TWO SUM问题?",
"knowledge": ["programming", "algorithms"],
"expert": "算法专家",
"steps": [
"定义TWO SUM问题",
"解释TWO SUM问题的背景和应用场景"
]
},
{
"query": "TWO SUM问题的输入是什么?",
"knowledge": ["programming", "algorithms"],
"expert": "算法专家",
"steps": [
"描述TWO SUM问题的输入格式",
"解释输入的含义"
]
},
{
"query": "TWO SUM问题的输出是什么?",
"knowledge": ["programming", "algorithms"],
"expert": "算法专家",
"steps": [
"描述TWO SUM问题的输出格式",
"解释输出的含义"
]
},
{
"query": "TWO SUM问题的目标是什么?",
"knowledge": ["programming", "algorithms"],
"expert": "算法专家",
"steps": [
"解释TWO SUM问题的目标",
"描述如何判断目标是否达成"
]
},
{
"query": "解决TWO SUM问题的常见算法有哪些?",
"knowledge": ["programming", "algorithms"],
"expert": "算法专家",
"steps": [
"列举常见的解决TWO SUM问题的算法",
"比较不同算法的优缺点"
]
},
{
"query": "如何实现TWO SUM问题的解决方案?",
"knowledge": ["programming", "algorithms"],
"expert": "算法专家",
"steps": [
"选择一种算法",
"详细描述算法的实现步骤",
"提供代码示例"
]
},
{
"query": "TWO SUM问题的复杂度是多少?",
"knowledge": ["programming", "algorithms"],
"expert": "算法专家",
"steps": [
"分析所选算法的复杂度",
"解释复杂度的含义"
]
}
],
"answers": [
{
"query": "什么是TWO SUM问题?",
"steps": [
"TWO SUM问题是一个经典的算法问题,目标是在一个数组中找到两个数,使得它们的和等于给定的目标值。",
"这个问题在编程面试中经常出现,因为它涉及基本的数组操作和哈希表的使用。"
],
"answer": "TWO SUM问题是一个经典的算法问题,目标是在一个数组中找到两个数,使得它们的和等于给定的目标值。这个问题在编程面试中经常出现,因为它涉及基本的数组操作和哈希表的使用。"
},
{
"query": "TWO SUM问题的输入是什么?",
"steps": [
"输入是一个整数数组和一个目标整数。",
"数组中的每个元素代表一个可能的加数,目标整数是两个加数的和。"
],
"answer": "TWO SUM问题的输入是一个整数数组和一个目标整数。数组中的每个元素代表一个可能的加数,目标整数是两个加数的和。"
},
{
"query": "TWO SUM问题的输出是什么?",
"steps": [
"输出是两个整数的索引,这两个整数的和等于目标值。",
"如果找不到这样的两个数,通常返回一个特定的值(如空数组或null)。"
],
"answer": "TWO SUM问题的输出是两个整数的索引,这两个整数的和等于目标值。如果找不到这样的两个数,通常返回一个特定的值(如空数组或null)。"
},
{
"query": "TWO SUM问题的目标是什么?",
"steps": [
"目标是在数组中找到两个数,使得它们的和等于目标值。",
"如果存在多个解,通常只返回一个解即可。"
],
"answer": "TWO SUM问题的目标是在数组中找到两个数,使得它们的和等于目标值。如果存在多个解,通常只返回一个解即可。"
},
{
"query": "解决TWO SUM问题的常见算法有哪些?",
"steps": [
"暴力法:通过双重循环遍历所有可能的数对,时间复杂度为O(n^2)。",
"哈希表法:使用哈希表存储数组元素及其索引,时间复杂度为O(n)。"
],
"answer": "解决TWO SUM问题的常见算法有暴力法和哈希表法。暴力法通过双重循环遍历所有可能的数对,时间复杂度为O(n^2);哈希表法使用哈希表存储数组元素及其索引,时间复杂度为O(n)。"
},
{
"query": "如何实现TWO SUM问题的解决方案?",
"steps": [
"选择哈希表法,因为它的时间复杂度较低。",
"遍历数组,对于每个元素,检查哈希表中是否存在目标值减去当前元素的值。",
"如果存在,返回当前元素的索引和哈希表中对应元素的索引。",
"如果不存在,将当前元素及其索引存入哈希表。",
"代码示例:\n```python\ndef two_sum(nums, target):\n hash_map = {}\n for i, num in enumerate(nums):\n complement = target - num\n if complement in hash_map:\n return [hash_map[complement], i]\n hash_map[num] = i\n return []\n```"
],
"answer": "选择哈希表法,因为它的时间复杂度较低。遍历数组,对于每个元素,检查哈希表中是否存在目标值减去当前元素的值。如果存在,返回当前元素的索引和哈希表中对应元素的索引。如果不存在,将当前元素及其索引存入哈希表。代码示例:\n```python\ndef two_sum(nums, target):\n hash_map = {}\n for i, num in enumerate(nums):\n complement = target - num\n if complement in hash_map:\n return [hash_map[complement], i]\n hash_map[num] = i\n return []\n```"
},
{
"query": "TWO SUM问题的复杂度是多少?",
"steps": [
"哈希表法的时间复杂度为O(n),因为只需要遍历数组一次。",
"空间复杂度为O(n),因为需要存储哈希表。"
],
"answer": "哈希表法的时间复杂度为O(n),因为只需要遍历数组一次。空间复杂度为O(n),因为需要存储哈希表。"
}
],
"final_answer": "要解决TWO SUM问题,可以使用哈希表法。该方法的时间复杂度为O(n),空间复杂度为O(n)。具体步骤是遍历数组,对于每个元素,检查哈希表中是否存在目标值减去当前元素的值。如果存在,返回当前元素的索引和哈希表中对应元素的索引。如果不存在,将当前元素及其索引存入哈希表。代码示例:\n```python\ndef two_sum(nums, target):\n hash_map = {}\n for i, num in enumerate(nums):\n complement = target - num\n if complement in hash_map:\n return [hash_map[complement], i]\n hash_map[num] = i\n return []\n```"
}
总结
-
现在的实现方案是基于问题分解, 效果不太好
-
在最开始写analyze的时候, 我希望通过递归的写法来分解子问题, 直到问题无法分解, 当代码逻辑过于复杂的时候, 已经此时很难"执行"代码了
-
问题的分解是回答效果的关键, 应该有重点的进行问题的分解, 现在的写法还有很大的问题, 问题的分解不是越细越好
-
后续的实现逻辑应该是基于问题理解, 解决路径规划 这两个步骤
- 问题可能的坑是什么? 问题中有哪些最关键的定义/概念?
- 需要做哪些事情来解决最关键的问题? 怎么在output中验证? 自查的部分
-
最后的答案总结, 用一个Summarize效果还是不好, 这里最好能够再写一个指令来处理.
-
自动 few-shot 是一个很有意思的探索领域, 在output中可以直接做的, 可以再试试效果, 牺牲output token数量, 来换取更好的结果, 一些场景下可能还是需要的