From c2e900ee545b4e8ad72fcdf50dc0ac1fdf0360a1 Mon Sep 17 00:00:00 2001 From: wenmeng zhou Date: Thu, 9 Nov 2023 20:04:16 +0800 Subject: [PATCH] update llm riddles (#624) * add multi online support * add readme and requirements.txt * refactor code to llm and challenges and update readme * handle llm exception and return empty str * update error handling * fix gradio version and add concurrency control * remove concurrency_limit * update reame --- examples/apps/llm_riddles/README.md | 8 +- examples/apps/llm_riddles/README_CN.md | 9 +- examples/apps/llm_riddles/app.py | 349 ++---------------- .../apps/llm_riddles/challenges/__init__.py | 0 examples/apps/llm_riddles/challenges/ch1.py | 70 ++++ examples/apps/llm_riddles/challenges/ch2.py | 94 +++++ examples/apps/llm_riddles/challenges/ch3.py | 67 ++++ examples/apps/llm_riddles/challenges/ch4.py | 55 +++ examples/apps/llm_riddles/llm.py | 98 +++++ examples/apps/llm_riddles/requirements.txt | 2 +- 10 files changed, 436 insertions(+), 316 deletions(-) create mode 100644 examples/apps/llm_riddles/challenges/__init__.py create mode 100644 examples/apps/llm_riddles/challenges/ch1.py create mode 100644 examples/apps/llm_riddles/challenges/ch2.py create mode 100644 examples/apps/llm_riddles/challenges/ch3.py create mode 100644 examples/apps/llm_riddles/challenges/ch4.py create mode 100644 examples/apps/llm_riddles/llm.py diff --git a/examples/apps/llm_riddles/README.md b/examples/apps/llm_riddles/README.md index 445d2364..5d446c81 100644 --- a/examples/apps/llm_riddles/README.md +++ b/examples/apps/llm_riddles/README.md @@ -1,7 +1,11 @@ # Oh No! I'm Surrounded by LLMs! (LLMRiddles) ## Project Introduction -"Oh No! I'm Surrounded by LLMs!" is an intellectual challenge game. We use GPT4 to automatically generate corresponding game code based on existing Large Language Model (LLM) dialogue Gradio application codes within the ModelScope community, combined with preset questions from the Zhihu article ["How to Accomplish Tasks with 'Impossible'"](https://zhuanlan.zhihu.com/p/665393240), creating a unique gameplay experience. In this stream, players are required to cleverly construct questions that challenge the LLM to provide answers that meet specific conditions. +"Oh No! I'm Surrounded by LLMs!" is an intellectual challenge game. We use LLM to automatically generate corresponding game code based on existing Large Language Model (LLM) dialogue Gradio application codes within the ModelScope community, combined with preset questions from the Zhihu article ["How to Accomplish Tasks with 'Impossible'"](https://zhuanlan.zhihu.com/p/665393240), creating a unique gameplay experience. In this stream, players are required to cleverly construct questions that challenge the LLM to provide answers that meet specific conditions. + +## News +November 7, 2023 - Released the initial demo version 🔥 +November 8, 2023 - Segregated level modules and LLM, enabling independent integration of levels and LLM. Pull Requests welcome 🔥 🔥 ## Getting Started @@ -40,7 +44,7 @@ We welcome everyone to contribute to "Oh No! I'm Surrounded by LLMs!", including We sincerely thank all community members who have contributed to this project, especially: - Idea from: [haoqiangfan](https://www.zhihu.com/people/haoqiang-fan) -- Most of the code is auto-generated by GPT-4 +- Most of the code is auto-generated by LLM ## Support If you encounter any problems or need assistance during the game, please submit your issues on the project's [Issues page](https://github.com/modelscope/modelscope/issues). diff --git a/examples/apps/llm_riddles/README_CN.md b/examples/apps/llm_riddles/README_CN.md index 08d7bf3c..2cd27417 100644 --- a/examples/apps/llm_riddles/README_CN.md +++ b/examples/apps/llm_riddles/README_CN.md @@ -1,7 +1,12 @@ # 完蛋!我被LLM包围了!(LLMRiddles) ## 项目简介 -《完蛋!我被LLM包围了!》是一款智力挑战游戏。该项目利用gpt4, 基于ModelScope社区内现有的LLM对话Gradio应用程序代码,结合知乎文章[《如何用“不可能”完成任务》](https://zhuanlan.zhihu.com/p/665393240)中的预设问题,自动生成了对应的游戏代码,创造了一个独特的游戏体验。在这个游戏中,玩家需要巧妙构造问题,挑战LLM给出满足特定条件的回答。 +《完蛋!我被LLM包围了!》是一款智力挑战游戏。该项目利用LLM代码生成, 基于ModelScope社区内现有的LLM对话Gradio应用程序代码,结合知乎文章[《如何用“不可能”完成任务》](https://zhuanlan.zhihu.com/p/665393240)中的预设问题,自动生成了对应的游戏代码,创造了一个独特的游戏体验。在这个游戏中,玩家需要巧妙构造问题,挑战LLM给出满足特定条件的回答。 + + +## 更新 +2023.11.7 发布初版demo🔥 +2023.11.8 拆分关卡模块和llm,支持关卡独立接入,llm独立接入, 欢迎PR 🔥 🔥 ## 开始游戏 @@ -40,7 +45,7 @@ 我们诚挚感谢所有对本项目做出贡献的社区成员,特别是: - idea来源: [haoqiangfan](https://www.zhihu.com/people/haoqiang-fan) -- 代码大部分来自于GPT4自动生成 +- 代码大部分来自于LLM自动生成 ## 支持 如果你在游戏过程中遇到任何问题或需要帮助,请通过项目的[Issues页面](https://github.com/modelscope/modelscope/issues)提交你的问题。 diff --git a/examples/apps/llm_riddles/app.py b/examples/apps/llm_riddles/app.py index dc5c1b7b..c48b2660 100644 --- a/examples/apps/llm_riddles/app.py +++ b/examples/apps/llm_riddles/app.py @@ -1,298 +1,24 @@ +import functools +import inspect import os import random import re -from http import HTTPStatus -import dashscope import gradio as gr -import sympy +from challenges.ch1 import challenge1 +from challenges.ch2 import challenge2 +from challenges.ch3 import challenge3 +from challenges.ch4 import challenge4 +from llm import create_model -dashscope.api_key = os.getenv('DASHSCOPE_API_KEY') +model_cache = {} # 定义关卡信息和验证逻辑 - - -# 辅助函数 - 检查是否为质数 -def is_prime(num): - return sympy.isprime(num) - - -# 辅助函数 - 获取下一个质数 -def next_prime(num): - return sympy.nextprime(num) - - -# 辅助函数 - 检查是否为平方数 -def is_square(n): - return sympy.sqrt(n).is_integer - - -# 辅助函数 - 获取平方根 -def get_square_root(n): - return int(sympy.sqrt(n)) - - -def validate_palindrome_invariance(origin_input, response): - """ - 验证器函数,检查对于给定的问题,正着问和倒着问的模型回答是否一致。 - - :param origin_input: 原始问题 - :return: True 如果回答一致,否则 False - """ - - # 将原始问题倒序并提交 - reversed_question = origin_input[::-1] - reversed_response = generate_response(reversed_question) - - # 比较两个回答是否一致 - return response.strip() == reversed_response.strip() - - -def validate_palindrome_inverse(origin_input, response): - """ - 验证器函数,检查对于给定的问题,正着问和倒着问的模型的回答本身不回文且也是逆序的关系。 - - :param origin_input: 原始问题 - :param response: 模型对原始问题的回答 - :param model_fn: 能够返回模型回答的函数 - :return: True 如果回答本身不回文且也是逆序关系,否则 False - """ - - # 检查原始输入不是回文 - if origin_input == origin_input[::-1]: - return False - - # 将原始问题倒序并获取模型回答 - reversed_question = origin_input[::-1] - reversed_response = generate_response(reversed_question).strip() - - # 检查原始回答不是回文 - if response == response[::-1]: - return False - - # 比较正着问和倒着问的回答是否是逆序关系 - return response == reversed_response[::-1] - - -def validate_reciprocal_question(input, response): - """ - 验证器函数,检查给定的问题A和回答B,是否能够通过以B作为新的提问得到原始问题A作为回答。 - - :param response: 模型对问题A的回答B - :param input: 原始问题A - :param model_fn: 模型调用函数,接收问题并返回回答 - :return: True 如果以回答B作为新的提问能得到问题A作为回答,否则 False - """ - - # 确保回答B不等于原始问题A - if response == input: - return False - - # 以回答B作为新的提问 - new_response = generate_response(response) - - # 检查新的回答是否与原始问题A相等 - return new_response.strip() == input.strip() - - challenges = [ - { - 'name': - '第一章 对话之趣', - 'problems': [ - { - 'title': '第1题 初来乍到', - 'description': '请你构造一个问题使模型的回答是一字不差的“1+1=3”(不需要引号)。', - 'validator': - lambda response, input: response.strip() == '1+1=3' - }, - { - 'title': - '第2题 小试牛刀', - 'description': - '请输入三个字以内的问题,使模型的回答在30个字以上。', - 'validator': - lambda response, input: len(input) <= 3 and len(response) > 30 - }, - { - 'title': - '第3题 短说长话', - 'description': - '请输入一个字的问题,使模型的回答在100个字以上。', - 'validator': - lambda response, input: len(input) == 1 and len(response) > 100 - }, - { - 'title': - '第4题 短说短话', - 'description': - '请输入一个字的问题,使模型的回答在20个字以内。', - 'validator': - lambda response, input: len(input) == 1 and len(response) <= 20 - }, - { - 'title': - '第5题 回文不变', - 'description': - '请输入一个本身不是回文串的问题,使无论正着问还是倒着问,模型的回答是一样的。', - # 验证器将取决于模型的行为,这里只是一个占位符 - 'validator': - lambda response, input: validate_palindrome_invariance( - input, response) - }, - { - 'title': - '第6题 无中生狗', - 'description': - '请提一个不包含“狗”这个字的问题,但是回答中至少出现3次“狗”这个字。', - 'validator': - lambda response, input: '狗' not in input and response.count( - '狗') >= 3 - }, - ] - }, - { - 'name': - '第二章 数字游戏', - 'problems': [ - { - 'title': - '第1题 质数长度', - 'description': - '你需要提出一个字数是质数的问题,使回答的长度刚好是它的下一个质数。', - 'validator': - lambda response, input: is_prime(len(input)) and len(response) - == next_prime(len(input)) - }, - { - 'title': - '第2题 越说越大', - 'description': - '请输入一个大于一的正整数作为问题,使回答里包含至少比它大一千的数。', - 'validator': - lambda response, input: - (input.isdigit() and int(input) > 1 and any( - int(num) > int(input) + 1000 - for num in re.findall(r'\d+', response))) - }, - { - 'title': - '第3题 越说越小', - 'description': - '请输入一个大于一的正整数作为问题,使回答里包含至少10个不同的至少比它小一千的自然数。', - 'validator': - lambda response, input: - (input.isdigit() and int(input) > 1 and (len({ - int(num) - for num in re.findall(r'\d+', response) - if int(num) < int(input) - 1000 - }) >= 10)) - }, - { - 'title': - '第4题 警世名言', - 'description': - '请在不提及1,4,5这三个字符的情况下让模型回答114514,不要有多余的字符。', - 'validator': - lambda response, input: response.strip() == '114514' and all( - c not in input for c in ['1', '4', '5']) - }, - { - 'title': - '第5题 开平方数', - 'description': - '请输入一个大于一的平方数作为问题,使回答里包含它的平方根对应的数。', - 'validator': - lambda response, input: - (input.isdigit() and int(input) > 1 and is_square(int(input)) - and str(get_square_root(int(input))) in response) - }, - { - 'title': - '第6题 得寸进狗', - 'description': - '请提一个不包含“狗”这个字的10个字以内的问题,使回答中“狗”这个字出现的次数至少是问题字数的2倍。', - 'validator': - lambda response, input: '狗' not in input and len(input) <= 10 - and response.count('狗') >= 2 * len(input) - }, - ] - }, - { - 'name': - '第三章 巅峰挑战', - 'problems': [ - { - 'title': - '第1题 自说自话', - 'description': - '请做出一个至少10个字的提问,使回答和提问完全一样。', - 'validator': - lambda response, input: input == response and len(input) >= 10 - }, - { - 'title': - '第2题 话自说自', - 'description': - '请做出一个至少10个字的提问,使回答是提问的逆序。', - 'validator': - lambda response, input: input[::-1] == response and len(input) - >= 10 - }, - { - 'title': - '第3题 就是敢说', - 'description': - '请在不提及1,3这三个字符的情况下让模型回答1+1=3,不要有多余的字符。', - 'validator': - lambda response, input: response.strip() == '1+1=3' and all( - c not in input for c in ['1', '3']) - }, - { - 'title': - '第4题 回文协变', - 'description': - '请输入一个本身不是回文串的问题,使正着问和倒着问的时候,模型的回答本身不回文且也是逆序的关系。', - 'validator': - lambda response, input: validate_palindrome_inverse( - input, response) - }, - ] - }, - { - 'name': - '第四章 无人之境', - 'problems': [ - { - 'title': - '第1题 循环往复', - 'description': - '请构造一个问题A,它的回复是不同于A的B,然后以B提问能再次得到A.', - 'validator': - lambda response, input: validate_reciprocal_question( - input, response) - }, - { - 'title': - '第2题 惜字如金', - 'description': - '请输入一个字的问题,使模型的回答在16个字以内。', - 'validator': - lambda response, input: len(input) == 1 and len(response) <= 16 - }, - { - 'title': - '第3题 自然之密', - 'description': - '请输入一个大于一的正整数作为问题,使回答里包含和它刚好相差1的数。', - 'validator': - lambda response, input: - (input.isdigit() and int(input) > 1 and any( - abs(int(num) - int(input)) == 1 - for num in re.findall(r'\d+', response))) - }, - ] - } + challenge1, + challenge2, + challenge3, + challenge4, ] @@ -315,8 +41,7 @@ def update_question_info(current_chapter_index, current_challenge_index): return question_info -def validate_challenge(response, input, state): - print('in validate_challenge') +def validate_challenge(response, input, state, generate_response): assert 'current_chapter_index' in state, 'current_chapter_index not found in state' assert 'current_challenge_index' in state, 'current_challenge_index not found in state' current_chapter_index = state['current_chapter_index'] @@ -326,7 +51,14 @@ def validate_challenge(response, input, state): # 获取当前挑战 challenge = current_chapter['problems'][current_challenge_index] - if challenge['validator'](response, input): + validate_fn = challenge['validator'] + params = inspect.signature(validate_fn).parameters + if 'generate_response' in params: + valid_result = validate_fn(response, input, generate_response) + else: + valid_result = validate_fn(response, input) + + if valid_result: challenge_result = '挑战成功!进入下一关。' # 检查是否还有更多挑战在当前章节 if current_challenge_index < len(current_chapter['problems']) - 1: @@ -350,34 +82,29 @@ def validate_challenge(response, input, state): update_challenge_info(current_chapter_index, current_challenge_index) -def generate_response(input): - messages = [{ - 'role': 'system', - 'content': """You are a helpful assistant.""" - }, { - 'role': 'user', - 'content': input - }] - response = dashscope.Generation.call( - model='qwen-max', - messages=messages, - # set the random seed, optional, default to 1234 if not set - seed=random.randint(1, 10000), - result_format='message', # set the result to be "message" format. - top_p=0.8) - if response.status_code == HTTPStatus.OK: - return response.output.choices[0].message.content +def generate_response(input, model_name): + if model_name in model_cache: + model = model_cache[model_name] else: - gr.Error('网络连接错误,请重试。') + model = create_model(model_name) + model_cache[model_name] = model + + try: + return model(input) + except RuntimeError as e: + # if exception happens, print error in log and return empty str + print('error', e) + return '' def on_submit(input, state): - response = generate_response(input) + model_name = 'qwen-plus' + gen_fn = functools.partial(generate_response, model_name=model_name) + response = gen_fn(input) history = [(input, response)] print(history) challenge_result, question_info, challenge_info = validate_challenge( - response, input, state) - print('validate_challenge done') + response, input, state, gen_fn) return challenge_result, history, question_info, challenge_info @@ -414,4 +141,4 @@ with block as demo: inputs=[message, state], outputs=[challenge_result, chatbot, question_info, challenge_info]) -demo.queue().launch(height=800, share=True) +demo.queue(concurrency_count=10).launch(height=800, share=True) diff --git a/examples/apps/llm_riddles/challenges/__init__.py b/examples/apps/llm_riddles/challenges/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/examples/apps/llm_riddles/challenges/ch1.py b/examples/apps/llm_riddles/challenges/ch1.py new file mode 100644 index 00000000..251cc7b1 --- /dev/null +++ b/examples/apps/llm_riddles/challenges/ch1.py @@ -0,0 +1,70 @@ +def validate_palindrome_invariance(origin_input, respons, generate_response): + """ + 验证器函数,检查对于给定的问题,正着问和倒着问的模型回答是否一致。 + + :param origin_input: 原始问题 + :return: True 如果回答一致,否则 False + """ + + # 将原始问题倒序并提交 + reversed_question = origin_input[::-1] + reversed_response = generate_response(reversed_question) + + # 比较两个回答是否一致 + return response.strip() == reversed_response.strip() + + +challenge1 = { + 'name': + '第一章 对话之趣', + 'problems': [ + { + 'title': '第1题 初来乍到', + 'description': '请你构造一个问题使模型的回答是一字不差的“1+1=3”(不需要引号)。', + 'validator': lambda response, input: response.strip() == '1+1=3' + }, + { + 'title': + '第2题 小试牛刀', + 'description': + '请输入三个字以内的问题,使模型的回答在30个字以上。', + 'validator': + lambda response, input: len(input) <= 3 and len(response) > 30 + }, + { + 'title': + '第3题 短说长话', + 'description': + '请输入一个字的问题,使模型的回答在100个字以上。', + 'validator': + lambda response, input: len(input) == 1 and len(response) > 100 + }, + { + 'title': + '第4题 短说短话', + 'description': + '请输入一个字的问题,使模型的回答在20个字以内。', + 'validator': + lambda response, input: len(input) == 1 and len(response) <= 20 + }, + { + 'title': + '第5题 回文不变', + 'description': + '请输入一个本身不是回文串的问题,使无论正着问还是倒着问,模型的回答是一样的。', + # 验证器将取决于模型的行为,这里只是一个占位符 + 'validator': + lambda response, input: validate_palindrome_invariance( + input, response) + }, + { + 'title': + '第6题 无中生狗', + 'description': + '请提一个不包含“狗”这个字的问题,但是回答中至少出现3次“狗”这个字。', + 'validator': + lambda response, input: '狗' not in input and response.count('狗') \ + >= 3 + }, + ] +} diff --git a/examples/apps/llm_riddles/challenges/ch2.py b/examples/apps/llm_riddles/challenges/ch2.py new file mode 100644 index 00000000..fffd9a38 --- /dev/null +++ b/examples/apps/llm_riddles/challenges/ch2.py @@ -0,0 +1,94 @@ +import re + +import sympy + + +# 辅助函数 - 检查是否为质数 +def is_prime(num): + return sympy.isprime(num) + + +# 辅助函数 - 获取下一个质数 +def next_prime(num): + return sympy.nextprime(num) + + +# 辅助函数 - 检查是否为平方数 +def is_square(n): + return sympy.sqrt(n).is_integer + + +# 辅助函数 - 获取平方根 +def get_square_root(n): + return int(sympy.sqrt(n)) + + +challenge2 = { + 'name': + '第二章 数字游戏', + 'problems': [ + { + 'title': + '第1题 质数长度', + 'description': + '你需要提出一个字数是质数的问题,使回答的长度刚好是它的下一个质数。', + 'validator': + lambda response, input: + (is_prime(len(input)) and len(response) == next_prime(len(input))) + }, + { + 'title': + '第2题 越说越大', + 'description': + '请输入一个大于一的正整数作为问题,使回答里包含至少比它大一千的数。', + 'validator': + lambda response, input: + (input.isdigit() and int(input) > 1 and any( + int(num) > int(input) + 1000 + for num in re.findall(r'\d+', response))) + }, + { + 'title': + '第3题 越说越小', + 'description': + '请输入一个大于一的正整数作为问题,使回答里包含至少10个不同的至少比它小一千的自然数。', + 'validator': + lambda response, input: + (input.isdigit() and int(input) > 1 and (len({ + int(num) + for num in re.findall(r'\d+', response) + if int(num) < int(input) - 1000 + }) >= 10)) + }, + { + 'title': + '第4题 警世名言', + 'description': + '请在不提及1,4,5这三个字符的情况下让模型回答114514,不要有多余的字符。', + 'validator': + lambda response, input: response.strip() == '114514' and all( + c not in input for c in ['1', '4', '5']) + }, + { + 'title': + '第5题 开平方数', + 'description': + '请输入一个大于一的平方数作为问题,使回答里包含它的平方根对应的数。', + 'validator': + lambda response, input: (( + input.isdigit() and int(input) > 1 and is_square(int(input)) + and # noqa: W504 + str(get_square_root(int(input))) in response)) + }, + { + 'title': + '第6题 得寸进狗', + 'description': + '请提一个不包含“狗”这个字的10个字以内的问题,使回答中“狗”这个字出现的次数至少是问题字数的2倍。', + 'validator': + lambda response, input: ( + '狗' not in input and len(input) <= 10 and # noqa: W504 + response.count('狗') >= 2 * len(input)) + }, + ] +} diff --git a/examples/apps/llm_riddles/challenges/ch3.py b/examples/apps/llm_riddles/challenges/ch3.py new file mode 100644 index 00000000..67c477ef --- /dev/null +++ b/examples/apps/llm_riddles/challenges/ch3.py @@ -0,0 +1,67 @@ +def validate_palindrome_inverse(origin_input, response, generate_response): + """ + 验证器函数,检查对于给定的问题,正着问和倒着问的模型的回答本身不回文且也是逆序的关系。 + + :param origin_input: 原始问题 + :param response: 模型对原始问题的回答 + :param model_fn: 能够返回模型回答的函数 + :return: True 如果回答本身不回文且也是逆序关系,否则 False + """ + + # 检查原始输入不是回文 + if origin_input == origin_input[::-1]: + return False + + # 将原始问题倒序并获取模型回答 + reversed_question = origin_input[::-1] + reversed_response = generate_response(reversed_question).strip() + + # 检查原始回答不是回文 + if response == response[::-1]: + return False + + # 比较正着问和倒着问的回答是否是逆序关系 + return response == reversed_response[::-1] + + +challenge3 = { + 'name': + '第三章 巅峰挑战', + 'problems': [ + { + 'title': + '第1题 自说自话', + 'description': + '请做出一个至少10个字的提问,使回答和提问完全一样。', + 'validator': + lambda response, input: input == response and len(input) >= 10 + }, + { + 'title': + '第2题 话自说自', + 'description': + '请做出一个至少10个字的提问,使回答是提问的逆序。', + 'validator': + lambda response, input: + (input[::-1] == response and len(input) >= 10) + }, + { + 'title': + '第3题 就是敢说', + 'description': + '请在不提及1,3这三个字符的情况下让模型回答1+1=3,不要有多余的字符。', + 'validator': + lambda response, input: response.strip() == '1+1=3' and all( + c not in input for c in ['1', '3']) + }, + { + 'title': + '第4题 回文协变', + 'description': + '请输入一个本身不是回文串的问题,使正着问和倒着问的时候,模型的回答本身不回文且也是逆序的关系。', + 'validator': + lambda response, input: validate_palindrome_inverse( + input, response) + }, + ] +}, diff --git a/examples/apps/llm_riddles/challenges/ch4.py b/examples/apps/llm_riddles/challenges/ch4.py new file mode 100644 index 00000000..0108a7bd --- /dev/null +++ b/examples/apps/llm_riddles/challenges/ch4.py @@ -0,0 +1,55 @@ +def validate_reciprocal_question(input, response, generate_response): + """ + 验证器函数,检查给定的问题A和回答B,是否能够通过以B作为新的提问得到原始问题A作为回答。 + + :param response: 模型对问题A的回答B + :param input: 原始问题A + :param model_fn: 模型调用函数,接收问题并返回回答 + :return: True 如果以回答B作为新的提问能得到问题A作为回答,否则 False + """ + + # 确保回答B不等于原始问题A + if response == input: + return False + + # 以回答B作为新的提问 + new_response = generate_response(response) + + # 检查新的回答是否与原始问题A相等 + return new_response.strip() == input.strip() + + +challenge4 = { + 'name': + '第四章 无人之境', + 'problems': [ + { + 'title': + '第1题 循环往复', + 'description': + '请构造一个问题A,它的回复是不同于A的B,然后以B提问能再次得到A.', + 'validator': + lambda response, input: validate_reciprocal_question( + input, response) + }, + { + 'title': + '第2题 惜字如金', + 'description': + '请输入一个字的问题,使模型的回答在16个字以内。', + 'validator': + lambda response, input: len(input) == 1 and len(response) <= 16 + }, + { + 'title': + '第3题 自然之密', + 'description': + '请输入一个大于一的正整数作为问题,使回答里包含和它刚好相差1的数。', + 'validator': + lambda response, input: + (input.isdigit() and int(input) > 1 and any( + abs(int(num) - int(input)) == 1 + for num in re.findall(r'\d+', response))) + }, + ] +} diff --git a/examples/apps/llm_riddles/llm.py b/examples/apps/llm_riddles/llm.py new file mode 100644 index 00000000..8965ee8f --- /dev/null +++ b/examples/apps/llm_riddles/llm.py @@ -0,0 +1,98 @@ +import os +import random +from http import HTTPStatus +from typing import Any, Dict, List, Union + + +class DashScope: + """A class to interact with the Dashscope AI service for response generation. + + This class provides an interface to call a specific model from the Dashscope service + to generate responses based on the input provided. + + Attributes: + model (str): The name of the model to be used for generation. + """ + + def __init__(self, model_name: str = 'qwen-plus'): + """Initializes the DashScope instance with a given model name. + + The constructor sets up the model name that will be used for response generation + and initializes the Dashscope API key from environment variables. + + Args: + model_name (str): The name of the model to be used. Defaults to 'qwen-plus'. + """ + import dashscope # Import dashscope module at runtime + dashscope.api_key = os.getenv( + 'DASHSCOPE_API_KEY') # Set the API key from environment variable + self.model: str = model_name # Assign the model name to an instance variable + + def __call__(self, input: Union[str, List[Dict[str, str]]], + **kwargs: Any) -> Union[str, None]: + """Allows the DashScope instance to be called as a function. + + This method processes the input, sends it to the Dashscope service, and returns + the generated response. + + Args: + input (Union[str, List[Dict[str, str]]]): The input str to generate a + response for. Can be a string or a list of messages. + **kwargs: Arbitrary keyword arguments. + + Returns: + Union[str, None]: The generated response from the model, or None if there is an error. + + Raises: + RuntimeError: If there is an error in accessing the Dashscope service. + """ + import dashscope # Import dashscope module at runtime + # Format the input into the required structure + if isinstance(input, str): + messages: List[Dict[str, str]] = [{ + 'role': + 'system', + 'content': + 'You are a helpful assistant.' + }, { + 'role': 'user', + 'content': input + }] + else: + messages = input + + # Make a call to the Dashscope service with the processed input + response = dashscope.Generation.call( + model=self.model, + messages=messages, + seed=random.randint(1, + 10000), # Generate a random seed for each call + result_format='message', # Specify the format of the result + top_p=kwargs.get('top_p', + 0.8) # Set the nucleus sampling parameter + ) + # Check the response status code and return the generated response or raise an error + if response.status_code == HTTPStatus.OK: + return response.output.choices[0].message.content + else: + print('Error accessing dashscope, please try again.', + response.request_id, response.message) + return '' + + +def create_model(model_name: str): + """Factory function to create a DashScope model instance based on the model name. + + Args: + model_name (str): The name of the model to create an instance of. + + Returns: + DashScope: An instance of the DashScope class. + + Raises: + ValueError: If the model name provided does not start with 'qwen'. + """ + if model_name.startswith('qwen'): + return DashScope(model_name) + else: + raise ValueError('Other model implementations need to be provided.') diff --git a/examples/apps/llm_riddles/requirements.txt b/examples/apps/llm_riddles/requirements.txt index 371f609b..76c33f1b 100644 --- a/examples/apps/llm_riddles/requirements.txt +++ b/examples/apps/llm_riddles/requirements.txt @@ -1,3 +1,3 @@ dashscope -gradio +gradio==3.39.0 sympy