Spaces:
Sleeping
Sleeping
Commit
·
0ec61d2
0
Parent(s):
update
Browse files- .gitattributes +36 -0
- .gitignore +14 -0
- Dockerfile +26 -0
- README.md +11 -0
- examples/api_eval/eval_gemini.py +120 -0
- examples/api_eval/eval_gemini_google.py +156 -0
- examples/api_eval/eval_openai.py +162 -0
- examples/live_api_native_audio.py +299 -0
- examples/quickstarts/Get_started_LiveAPI_NativeAudio.py +170 -0
- examples/qwen/quick_start/main.py +133 -0
- examples/qwen/quick_start/omni_realtime_client.py +259 -0
- examples/qwen/qwen_omni_realtime.py +425 -0
- examples/tutorial/text_generation.py +49 -0
- examples/vertexai_start/gemini-2.0-test.py +48 -0
- examples/vertexai_start/gemini-2.5-test.py +44 -0
- install.sh +62 -0
- log.py +229 -0
- main.py +70 -0
- project_settings.py +21 -0
- requirements.txt +11 -0
- toolbox/__init__.py +6 -0
- toolbox/json/__init__.py +6 -0
- toolbox/json/misc.py +63 -0
- toolbox/os/__init__.py +6 -0
- toolbox/os/command.py +59 -0
- toolbox/os/environment.py +114 -0
- toolbox/os/other.py +9 -0
.gitattributes
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
*.7z filter=lfs diff=lfs merge=lfs -text
|
| 2 |
+
*.arrow filter=lfs diff=lfs merge=lfs -text
|
| 3 |
+
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 4 |
+
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
| 5 |
+
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 6 |
+
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 7 |
+
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 8 |
+
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 9 |
+
*.joblib filter=lfs diff=lfs merge=lfs -text
|
| 10 |
+
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
| 11 |
+
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 12 |
+
*.model filter=lfs diff=lfs merge=lfs -text
|
| 13 |
+
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 14 |
+
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 15 |
+
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 16 |
+
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 17 |
+
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 18 |
+
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 19 |
+
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 20 |
+
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 21 |
+
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 22 |
+
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 23 |
+
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 24 |
+
*.rar filter=lfs diff=lfs merge=lfs -text
|
| 25 |
+
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 26 |
+
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
| 27 |
+
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
| 28 |
+
*.tar filter=lfs diff=lfs merge=lfs -text
|
| 29 |
+
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 30 |
+
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 31 |
+
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 32 |
+
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 33 |
+
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
+
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
+
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
*.wav filter=lfs diff=lfs merge=lfs -text
|
.gitignore
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
.git/
|
| 3 |
+
.idea/
|
| 4 |
+
|
| 5 |
+
/data/
|
| 6 |
+
/dotenv/
|
| 7 |
+
/logs/
|
| 8 |
+
/trained_models
|
| 9 |
+
/temp/
|
| 10 |
+
|
| 11 |
+
**/__pycache__/
|
| 12 |
+
|
| 13 |
+
#**/*.wav
|
| 14 |
+
**/*.xlsx
|
Dockerfile
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.12-slim
|
| 2 |
+
|
| 3 |
+
WORKDIR /code
|
| 4 |
+
|
| 5 |
+
COPY . /code
|
| 6 |
+
|
| 7 |
+
RUN apt-get update
|
| 8 |
+
RUN apt-get install -y wget unzip ffmpeg build-essential
|
| 9 |
+
|
| 10 |
+
RUN pip install --upgrade pip
|
| 11 |
+
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
|
| 12 |
+
|
| 13 |
+
RUN useradd -m -u 1000 user
|
| 14 |
+
|
| 15 |
+
USER user
|
| 16 |
+
|
| 17 |
+
ENV HOME=/home/user \
|
| 18 |
+
PATH=/home/user/.local/bin:$PATH
|
| 19 |
+
|
| 20 |
+
WORKDIR $HOME/app
|
| 21 |
+
|
| 22 |
+
COPY --chown=user . $HOME/app
|
| 23 |
+
|
| 24 |
+
RUN bash install.sh --stage 1 --stop_stage 1 --system_version ubuntu
|
| 25 |
+
|
| 26 |
+
CMD ["python3", "main.py"]
|
README.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: Open Gemini API
|
| 3 |
+
emoji: 🐨
|
| 4 |
+
colorFrom: purple
|
| 5 |
+
colorTo: pink
|
| 6 |
+
sdk: docker
|
| 7 |
+
pinned: false
|
| 8 |
+
license: apache-2.0
|
| 9 |
+
---
|
| 10 |
+
|
| 11 |
+
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
examples/api_eval/eval_gemini.py
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/python3
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
import argparse
|
| 4 |
+
import json
|
| 5 |
+
|
| 6 |
+
from openai import OpenAI
|
| 7 |
+
|
| 8 |
+
from project_settings import environment, project_path
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
def get_args():
|
| 12 |
+
parser = argparse.ArgumentParser()
|
| 13 |
+
parser.add_argument(
|
| 14 |
+
"--gemini_api_key",
|
| 15 |
+
default=environment.get(key="GEMINI_API_KEY"),
|
| 16 |
+
type=str
|
| 17 |
+
)
|
| 18 |
+
parser.add_argument(
|
| 19 |
+
"--model_name",
|
| 20 |
+
# default="gemini-2.5-pro",
|
| 21 |
+
default="gemini-2.5-flash",
|
| 22 |
+
type=str
|
| 23 |
+
)
|
| 24 |
+
parser.add_argument(
|
| 25 |
+
"--eval_data",
|
| 26 |
+
default=(project_path / "data/arc-easy.jsonl").as_posix(),
|
| 27 |
+
type=str
|
| 28 |
+
)
|
| 29 |
+
parser.add_argument(
|
| 30 |
+
"--eval_result",
|
| 31 |
+
default=(project_path / "data/eval_math_result.jsonl").as_posix(),
|
| 32 |
+
type=str
|
| 33 |
+
)
|
| 34 |
+
args = parser.parse_args()
|
| 35 |
+
return args
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def main():
|
| 39 |
+
args = get_args()
|
| 40 |
+
|
| 41 |
+
client = OpenAI(
|
| 42 |
+
api_key=args.gemini_api_key,
|
| 43 |
+
base_url="https://generativelanguage.googleapis.com/v1beta"
|
| 44 |
+
)
|
| 45 |
+
|
| 46 |
+
total = 0
|
| 47 |
+
total_correct = 0
|
| 48 |
+
with open(args.eval_data, "r", encoding="utf-8") as fin, open(args.eval_result, "a+", encoding="utf-8") as fout:
|
| 49 |
+
for row in fin:
|
| 50 |
+
row = json.loads(row)
|
| 51 |
+
idx = row["id"]
|
| 52 |
+
question = row["question"]
|
| 53 |
+
choices = row["choices"]
|
| 54 |
+
answer_key = row["answerkey"]
|
| 55 |
+
|
| 56 |
+
instruct = "Complete this single-choice question."
|
| 57 |
+
|
| 58 |
+
choices_str = ""
|
| 59 |
+
for choice in choices:
|
| 60 |
+
label = choice["label"]
|
| 61 |
+
text = choice["text"]
|
| 62 |
+
choices_str += f"If you think the answer is `{text}` output: `{label}`\n"
|
| 63 |
+
|
| 64 |
+
prompt = f"""
|
| 65 |
+
{instruct}
|
| 66 |
+
|
| 67 |
+
Question:
|
| 68 |
+
{question}
|
| 69 |
+
|
| 70 |
+
Choices:
|
| 71 |
+
{choices_str}
|
| 72 |
+
|
| 73 |
+
Remember to output ONLY the corresponding letter.
|
| 74 |
+
Your output is:
|
| 75 |
+
""".strip()
|
| 76 |
+
# print(prompt)
|
| 77 |
+
response = client.chat.completions.create(
|
| 78 |
+
model="gemini-2.5-pro",
|
| 79 |
+
messages=[{"role": "user", "content": prompt}],
|
| 80 |
+
stream=False,
|
| 81 |
+
# max_tokens=1,
|
| 82 |
+
temperature=0.0,
|
| 83 |
+
# logit_bias={
|
| 84 |
+
# 32: 100,
|
| 85 |
+
# 33: 100,
|
| 86 |
+
# 34: 100,
|
| 87 |
+
# 35: 100,
|
| 88 |
+
# 36: 100,
|
| 89 |
+
# }
|
| 90 |
+
)
|
| 91 |
+
|
| 92 |
+
prediction = response.choices[0].message.content
|
| 93 |
+
|
| 94 |
+
correct = 1 if prediction == answer_key else 0
|
| 95 |
+
|
| 96 |
+
total += 1
|
| 97 |
+
total_correct += correct
|
| 98 |
+
score = total_correct / total
|
| 99 |
+
|
| 100 |
+
row_ = {
|
| 101 |
+
"id": idx,
|
| 102 |
+
"question": question,
|
| 103 |
+
"choices": choices,
|
| 104 |
+
"ground_true": answer_key,
|
| 105 |
+
"prediction": prediction,
|
| 106 |
+
"correct": correct,
|
| 107 |
+
"total": total,
|
| 108 |
+
"total_correct": total_correct,
|
| 109 |
+
"score": score,
|
| 110 |
+
}
|
| 111 |
+
row_ = json.dumps(row_, ensure_ascii=False)
|
| 112 |
+
fout.write(f"{row_}\n")
|
| 113 |
+
|
| 114 |
+
print(f"score: {score}")
|
| 115 |
+
|
| 116 |
+
return
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
if __name__ == "__main__":
|
| 120 |
+
main()
|
examples/api_eval/eval_gemini_google.py
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/python3
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
import argparse
|
| 4 |
+
import json
|
| 5 |
+
import os
|
| 6 |
+
import time
|
| 7 |
+
|
| 8 |
+
from google import genai
|
| 9 |
+
from google.genai import types
|
| 10 |
+
|
| 11 |
+
from project_settings import environment, project_path
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def get_args():
|
| 15 |
+
parser = argparse.ArgumentParser()
|
| 16 |
+
parser.add_argument(
|
| 17 |
+
"--google_application_credentials",
|
| 18 |
+
default=(project_path / "dotenv/potent-veld-462405-t3-8091a29b2894.json").as_posix(),
|
| 19 |
+
type=str
|
| 20 |
+
)
|
| 21 |
+
parser.add_argument(
|
| 22 |
+
"--model_name",
|
| 23 |
+
# default="gemini-2.5-pro",
|
| 24 |
+
default="gemini-2.5-flash",
|
| 25 |
+
type=str
|
| 26 |
+
)
|
| 27 |
+
parser.add_argument(
|
| 28 |
+
"--eval_data",
|
| 29 |
+
default=(project_path / "data/arc-easy.jsonl").as_posix(),
|
| 30 |
+
type=str
|
| 31 |
+
)
|
| 32 |
+
parser.add_argument(
|
| 33 |
+
"--eval_result",
|
| 34 |
+
default=(project_path / "data/eval_math_result.jsonl").as_posix(),
|
| 35 |
+
type=str
|
| 36 |
+
)
|
| 37 |
+
args = parser.parse_args()
|
| 38 |
+
return args
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def main():
|
| 42 |
+
args = get_args()
|
| 43 |
+
|
| 44 |
+
os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = args.google_application_credentials
|
| 45 |
+
|
| 46 |
+
client = genai.Client(
|
| 47 |
+
vertexai=True,
|
| 48 |
+
project="potent-veld-462405-t3",
|
| 49 |
+
location="global",
|
| 50 |
+
)
|
| 51 |
+
generate_content_config = types.GenerateContentConfig(
|
| 52 |
+
temperature=1,
|
| 53 |
+
top_p=0.95,
|
| 54 |
+
max_output_tokens=8192,
|
| 55 |
+
response_modalities=["TEXT"],
|
| 56 |
+
)
|
| 57 |
+
|
| 58 |
+
total = 0
|
| 59 |
+
total_correct = 0
|
| 60 |
+
|
| 61 |
+
# finished
|
| 62 |
+
finished_idx_set = set()
|
| 63 |
+
if os.path.exists(args.eval_result):
|
| 64 |
+
with open(args.eval_result, "r", encoding="utf-8") as f:
|
| 65 |
+
for row in f:
|
| 66 |
+
row = json.loads(row)
|
| 67 |
+
idx = row["id"]
|
| 68 |
+
total = row["total"]
|
| 69 |
+
total_correct = row["total_correct"]
|
| 70 |
+
finished_idx_set.add(idx)
|
| 71 |
+
print(f"finished count: {len(finished_idx_set)}")
|
| 72 |
+
|
| 73 |
+
with open(args.eval_data, "r", encoding="utf-8") as fin, open(args.eval_result, "a+", encoding="utf-8") as fout:
|
| 74 |
+
for row in fin:
|
| 75 |
+
if total > 20:
|
| 76 |
+
break
|
| 77 |
+
|
| 78 |
+
row = json.loads(row)
|
| 79 |
+
idx = row["id"]
|
| 80 |
+
question = row["question"]
|
| 81 |
+
choices = row["choices"]
|
| 82 |
+
answer_key = row["answerkey"]
|
| 83 |
+
|
| 84 |
+
if idx in finished_idx_set:
|
| 85 |
+
continue
|
| 86 |
+
finished_idx_set.add(idx)
|
| 87 |
+
|
| 88 |
+
instruct = "Complete this single-choice question."
|
| 89 |
+
|
| 90 |
+
choices_str = ""
|
| 91 |
+
for choice in choices:
|
| 92 |
+
label = choice["label"]
|
| 93 |
+
text = choice["text"]
|
| 94 |
+
choices_str += f"If you think the answer is `{text}` output: `{label}`\n"
|
| 95 |
+
|
| 96 |
+
prompt = f"""
|
| 97 |
+
{instruct}
|
| 98 |
+
|
| 99 |
+
Question:
|
| 100 |
+
{question}
|
| 101 |
+
|
| 102 |
+
Choices:
|
| 103 |
+
{choices_str}
|
| 104 |
+
|
| 105 |
+
Remember to output ONLY the corresponding letter.
|
| 106 |
+
Your output is:
|
| 107 |
+
""".strip()
|
| 108 |
+
# print(prompt)
|
| 109 |
+
|
| 110 |
+
contents = [
|
| 111 |
+
types.Content(
|
| 112 |
+
role="user",
|
| 113 |
+
parts=[
|
| 114 |
+
types.Part.from_text(text=prompt)
|
| 115 |
+
]
|
| 116 |
+
)
|
| 117 |
+
]
|
| 118 |
+
time_begin = time.time()
|
| 119 |
+
response: types.GenerateContentResponse = client.models.generate_content(
|
| 120 |
+
model=args.model_name,
|
| 121 |
+
contents=contents,
|
| 122 |
+
config=generate_content_config,
|
| 123 |
+
)
|
| 124 |
+
time_cost = time.time() - time_begin
|
| 125 |
+
try:
|
| 126 |
+
prediction = response.candidates[0].content.parts[0].text
|
| 127 |
+
except TypeError:
|
| 128 |
+
continue
|
| 129 |
+
correct = 1 if prediction == answer_key else 0
|
| 130 |
+
|
| 131 |
+
total += 1
|
| 132 |
+
total_correct += correct
|
| 133 |
+
score = total_correct / total
|
| 134 |
+
|
| 135 |
+
row_ = {
|
| 136 |
+
"id": idx,
|
| 137 |
+
"question": question,
|
| 138 |
+
"choices": choices,
|
| 139 |
+
"ground_true": answer_key,
|
| 140 |
+
"prediction": prediction,
|
| 141 |
+
"correct": correct,
|
| 142 |
+
"total": total,
|
| 143 |
+
"total_correct": total_correct,
|
| 144 |
+
"score": score,
|
| 145 |
+
"time_cost": time_cost,
|
| 146 |
+
}
|
| 147 |
+
row_ = json.dumps(row_, ensure_ascii=False)
|
| 148 |
+
fout.write(f"{row_}\n")
|
| 149 |
+
|
| 150 |
+
print(f"score: {score}")
|
| 151 |
+
|
| 152 |
+
return
|
| 153 |
+
|
| 154 |
+
|
| 155 |
+
if __name__ == "__main__":
|
| 156 |
+
main()
|
examples/api_eval/eval_openai.py
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/python3
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
import argparse
|
| 4 |
+
import asyncio
|
| 5 |
+
import base64
|
| 6 |
+
from enum import Enum
|
| 7 |
+
import json
|
| 8 |
+
import os
|
| 9 |
+
import queue
|
| 10 |
+
import threading
|
| 11 |
+
import time
|
| 12 |
+
from typing import Optional, Callable, List, Dict, Any
|
| 13 |
+
import websockets
|
| 14 |
+
|
| 15 |
+
import pyaudio
|
| 16 |
+
import openai
|
| 17 |
+
from openai import AzureOpenAI
|
| 18 |
+
|
| 19 |
+
from project_settings import environment, project_path
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def get_args():
|
| 23 |
+
parser = argparse.ArgumentParser()
|
| 24 |
+
parser.add_argument(
|
| 25 |
+
"--api_key",
|
| 26 |
+
default=environment.get(key="OPENAI_API_KEY"),
|
| 27 |
+
type=str
|
| 28 |
+
)
|
| 29 |
+
parser.add_argument(
|
| 30 |
+
"--model_name",
|
| 31 |
+
default="gpt-4o",
|
| 32 |
+
# default="gpt-4o-mini",
|
| 33 |
+
type=str
|
| 34 |
+
)
|
| 35 |
+
parser.add_argument(
|
| 36 |
+
"--eval_data",
|
| 37 |
+
default=(project_path / "data/arc-easy.jsonl").as_posix(),
|
| 38 |
+
type=str
|
| 39 |
+
)
|
| 40 |
+
parser.add_argument(
|
| 41 |
+
"--eval_result",
|
| 42 |
+
default=(project_path / "data/eval_math_result.jsonl").as_posix(),
|
| 43 |
+
type=str
|
| 44 |
+
)
|
| 45 |
+
args = parser.parse_args()
|
| 46 |
+
return args
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def main():
|
| 50 |
+
args = get_args()
|
| 51 |
+
|
| 52 |
+
# gpt-4o: 82
|
| 53 |
+
# gemini: 89
|
| 54 |
+
client = AzureOpenAI(
|
| 55 |
+
# api_key=args.api_key,
|
| 56 |
+
api_key="Dqt75blRABmhgrwhfcupd1rq44YqNuEgku8FcFFDrEljMq6gltf0JQQJ99BCACYeBjFXJ3w3AAABACOG2njW",
|
| 57 |
+
api_version="2025-01-01-preview",
|
| 58 |
+
azure_endpoint="https://west-us-chatgpt.openai.azure.com"
|
| 59 |
+
)
|
| 60 |
+
|
| 61 |
+
total = 0
|
| 62 |
+
total_correct = 0
|
| 63 |
+
|
| 64 |
+
# finished
|
| 65 |
+
finished_idx_set = set()
|
| 66 |
+
if os.path.exists(args.eval_result):
|
| 67 |
+
with open(args.eval_result, "r", encoding="utf-8") as f:
|
| 68 |
+
for row in f:
|
| 69 |
+
row = json.loads(row)
|
| 70 |
+
idx = row["id"]
|
| 71 |
+
total = row["total"]
|
| 72 |
+
total_correct = row["total_correct"]
|
| 73 |
+
finished_idx_set.add(idx)
|
| 74 |
+
print(f"finished count: {len(finished_idx_set)}")
|
| 75 |
+
|
| 76 |
+
with open(args.eval_data, "r", encoding="utf-8") as fin, open(args.eval_result, "a+", encoding="utf-8") as fout:
|
| 77 |
+
for row in fin:
|
| 78 |
+
if total > 20:
|
| 79 |
+
break
|
| 80 |
+
|
| 81 |
+
row = json.loads(row)
|
| 82 |
+
idx = row["id"]
|
| 83 |
+
question = row["question"]
|
| 84 |
+
choices = row["choices"]
|
| 85 |
+
answer_key = row["answerkey"]
|
| 86 |
+
|
| 87 |
+
if idx in finished_idx_set:
|
| 88 |
+
continue
|
| 89 |
+
finished_idx_set.add(idx)
|
| 90 |
+
|
| 91 |
+
instruct = "Complete this single-choice question."
|
| 92 |
+
|
| 93 |
+
choices_str = ""
|
| 94 |
+
for choice in choices:
|
| 95 |
+
label = choice["label"]
|
| 96 |
+
text = choice["text"]
|
| 97 |
+
choices_str += f"If you think the answer is `{text}` output: `{label}`\n"
|
| 98 |
+
|
| 99 |
+
prompt = f"""
|
| 100 |
+
{instruct}
|
| 101 |
+
|
| 102 |
+
Question:
|
| 103 |
+
{question}
|
| 104 |
+
|
| 105 |
+
Choices:
|
| 106 |
+
{choices_str}
|
| 107 |
+
|
| 108 |
+
Remember to output ONLY the corresponding letter.
|
| 109 |
+
Your output is:
|
| 110 |
+
""".strip()
|
| 111 |
+
# print(prompt)
|
| 112 |
+
try:
|
| 113 |
+
time_begin = time.time()
|
| 114 |
+
response = client.chat.completions.create(
|
| 115 |
+
model=args.model_name,
|
| 116 |
+
messages=[{"role": "user", "content": prompt}],
|
| 117 |
+
stream=False,
|
| 118 |
+
# max_tokens=1,
|
| 119 |
+
temperature=0.0,
|
| 120 |
+
# logit_bias={
|
| 121 |
+
# 32: 100,
|
| 122 |
+
# 33: 100,
|
| 123 |
+
# 34: 100,
|
| 124 |
+
# 35: 100,
|
| 125 |
+
# 36: 100,
|
| 126 |
+
# }
|
| 127 |
+
)
|
| 128 |
+
time_cost = time.time() - time_begin
|
| 129 |
+
except openai.BadRequestError as e:
|
| 130 |
+
print(f"request failed, error type: {type(e)}, error text: {str(e)}")
|
| 131 |
+
continue
|
| 132 |
+
|
| 133 |
+
prediction = response.choices[0].message.content
|
| 134 |
+
|
| 135 |
+
correct = 1 if prediction == answer_key else 0
|
| 136 |
+
|
| 137 |
+
total += 1
|
| 138 |
+
total_correct += correct
|
| 139 |
+
score = total_correct / total
|
| 140 |
+
|
| 141 |
+
row_ = {
|
| 142 |
+
"id": idx,
|
| 143 |
+
"question": question,
|
| 144 |
+
"choices": choices,
|
| 145 |
+
"ground_true": answer_key,
|
| 146 |
+
"prediction": prediction,
|
| 147 |
+
"correct": correct,
|
| 148 |
+
"total": total,
|
| 149 |
+
"total_correct": total_correct,
|
| 150 |
+
"score": score,
|
| 151 |
+
"time_cost": time_cost,
|
| 152 |
+
}
|
| 153 |
+
row_ = json.dumps(row_, ensure_ascii=False)
|
| 154 |
+
fout.write(f"{row_}\n")
|
| 155 |
+
|
| 156 |
+
print(f"score: {score}")
|
| 157 |
+
|
| 158 |
+
return
|
| 159 |
+
|
| 160 |
+
|
| 161 |
+
if __name__ == "__main__":
|
| 162 |
+
main()
|
examples/live_api_native_audio.py
ADDED
|
@@ -0,0 +1,299 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
https://ai.google.dev/gemini-api/docs/live?hl=zh-cn#python_1
|
| 3 |
+
https://github.com/google-gemini/cookbook/blob/main/quickstarts/Get_started_LiveAPI_NativeAudio.py
|
| 4 |
+
"""
|
| 5 |
+
import asyncio
|
| 6 |
+
import argparse
|
| 7 |
+
import sys
|
| 8 |
+
import traceback
|
| 9 |
+
|
| 10 |
+
from google import genai
|
| 11 |
+
from google.genai import types
|
| 12 |
+
import pyaudio
|
| 13 |
+
|
| 14 |
+
from project_settings import environment
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
if sys.version_info < (3, 11, 0):
|
| 18 |
+
import taskgroup, exceptiongroup
|
| 19 |
+
asyncio.TaskGroup = taskgroup.TaskGroup
|
| 20 |
+
asyncio.ExceptionGroup = exceptiongroup.ExceptionGroup
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def get_args():
|
| 24 |
+
parser = argparse.ArgumentParser()
|
| 25 |
+
parser.add_argument(
|
| 26 |
+
"--gemini_api_key",
|
| 27 |
+
default=environment.get(key="GEMINI_API_KEY"),
|
| 28 |
+
type=str
|
| 29 |
+
)
|
| 30 |
+
parser.add_argument(
|
| 31 |
+
"--model_name",
|
| 32 |
+
default="gemini-2.5-flash-preview-native-audio-dialog",
|
| 33 |
+
type=str
|
| 34 |
+
)
|
| 35 |
+
args = parser.parse_args()
|
| 36 |
+
return args
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
class GeminiNativeAudio(object):
|
| 40 |
+
def __init__(self,
|
| 41 |
+
gemini_api_key: str,
|
| 42 |
+
model_name: str = "gemini-2.5-flash-preview-native-audio-dialog",
|
| 43 |
+
system_instruction: str = None,
|
| 44 |
+
):
|
| 45 |
+
self.gemini_api_key = gemini_api_key
|
| 46 |
+
self.model_name = model_name
|
| 47 |
+
self.system_instruction = system_instruction
|
| 48 |
+
|
| 49 |
+
self.client = genai.Client(api_key=gemini_api_key)
|
| 50 |
+
self.gemini_config = types.LiveConnectConfig(
|
| 51 |
+
response_modalities=["AUDIO"],
|
| 52 |
+
# system_instruction=system_instruction,
|
| 53 |
+
# speech_config=types.SpeechConfig(
|
| 54 |
+
# language_code="cmn-CN",
|
| 55 |
+
# )
|
| 56 |
+
)
|
| 57 |
+
|
| 58 |
+
self.pya = pyaudio.PyAudio()
|
| 59 |
+
self.pya_format = pyaudio.paInt16
|
| 60 |
+
self.pya_channels = 1
|
| 61 |
+
self.pya_send_sample_rate = 16000
|
| 62 |
+
self.pya_receive_sample_rate = 24000
|
| 63 |
+
self.pya_chunk_size = 1024
|
| 64 |
+
|
| 65 |
+
self.audio_bot_queue = asyncio.Queue()
|
| 66 |
+
self.audio_user_queue = asyncio.Queue(maxsize=5)
|
| 67 |
+
|
| 68 |
+
self.user_audio_stream = None
|
| 69 |
+
self.session = None
|
| 70 |
+
|
| 71 |
+
async def listen_audio(self):
|
| 72 |
+
mic_info = self.pya.get_default_input_device_info()
|
| 73 |
+
self.user_audio_stream = await asyncio.to_thread(
|
| 74 |
+
self.pya.open,
|
| 75 |
+
format=self.pya_format,
|
| 76 |
+
channels=self.pya_channels,
|
| 77 |
+
rate=self.pya_send_sample_rate,
|
| 78 |
+
input=True,
|
| 79 |
+
input_device_index=mic_info["index"],
|
| 80 |
+
frames_per_buffer=self.pya_chunk_size,
|
| 81 |
+
)
|
| 82 |
+
kwargs = {}
|
| 83 |
+
while True:
|
| 84 |
+
data = await asyncio.to_thread(self.user_audio_stream.read, self.pya_chunk_size, **kwargs)
|
| 85 |
+
await self.audio_user_queue.put({"data": data, "mime_type": "audio/pcm"})
|
| 86 |
+
|
| 87 |
+
async def send_realtime(self):
|
| 88 |
+
while True:
|
| 89 |
+
msg = await self.audio_user_queue.get()
|
| 90 |
+
await self.session.send_realtime_input(audio=msg)
|
| 91 |
+
|
| 92 |
+
async def receive_audio(self):
|
| 93 |
+
while True:
|
| 94 |
+
turn = self.session.receive()
|
| 95 |
+
async for response in turn:
|
| 96 |
+
if data := response.data:
|
| 97 |
+
self.audio_bot_queue.put_nowait(data)
|
| 98 |
+
continue
|
| 99 |
+
if text := response.text:
|
| 100 |
+
print(text, end="")
|
| 101 |
+
|
| 102 |
+
# If you interrupt the model, it sends a turn_complete.
|
| 103 |
+
# For interruptions to work, we need to stop playback.
|
| 104 |
+
# So empty out the audio queue because it may have loaded
|
| 105 |
+
# much more audio than has played yet.
|
| 106 |
+
while not self.audio_bot_queue.empty():
|
| 107 |
+
self.audio_bot_queue.get_nowait()
|
| 108 |
+
|
| 109 |
+
async def play_audio(self):
|
| 110 |
+
stream = await asyncio.to_thread(
|
| 111 |
+
self.pya.open,
|
| 112 |
+
format=self.pya_format,
|
| 113 |
+
channels=self.pya_channels,
|
| 114 |
+
rate=self.pya_receive_sample_rate,
|
| 115 |
+
output=True,
|
| 116 |
+
)
|
| 117 |
+
while True:
|
| 118 |
+
bytestream = await self.audio_bot_queue.get()
|
| 119 |
+
await asyncio.to_thread(stream.write, bytestream)
|
| 120 |
+
|
| 121 |
+
async def run(self):
|
| 122 |
+
try:
|
| 123 |
+
async with (
|
| 124 |
+
self.client.aio.live.connect(model=self.model_name, config=self.gemini_config) as session,
|
| 125 |
+
asyncio.TaskGroup() as tg,
|
| 126 |
+
):
|
| 127 |
+
self.session = session
|
| 128 |
+
|
| 129 |
+
await self.session.send_realtime_input(text=self.system_instruction)
|
| 130 |
+
|
| 131 |
+
tg.create_task(self.send_realtime())
|
| 132 |
+
tg.create_task(self.listen_audio())
|
| 133 |
+
tg.create_task(self.receive_audio())
|
| 134 |
+
tg.create_task(self.play_audio())
|
| 135 |
+
|
| 136 |
+
except asyncio.CancelledError:
|
| 137 |
+
pass
|
| 138 |
+
except ExceptionGroup as EG:
|
| 139 |
+
if self.user_audio_stream:
|
| 140 |
+
self.user_audio_stream.close()
|
| 141 |
+
traceback.print_exception(EG)
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
INSTRUCTION = """
|
| 145 |
+
<身份>
|
| 146 |
+
您正在为客户提供中文学习服务。您来自全球最大的中文学习平台LingoAce,并且是一位资深销售。
|
| 147 |
+
<工作>
|
| 148 |
+
您正在通过电话与徐勋健交谈。
|
| 149 |
+
您的工作是询问 徐勋健 他/她的孩子中文学习的近况,以及孩子是否愿意继续学习。
|
| 150 |
+
如果您还不清楚,请继续提问。
|
| 151 |
+
<知识库 FAQ>
|
| 152 |
+
1.
|
| 153 |
+
问: 号码是哪里来的
|
| 154 |
+
答: 请您放心哈,因为您之前留过电话,所以我们才能联系到您,而且特地帮您争取了免费名额,您看要参加一下吗?;
|
| 155 |
+
2.
|
| 156 |
+
问: 询问价格
|
| 157 |
+
答: 这次给您申请的是免费名额,不收费哈,来电就是想邀请孩子免费体验中问水平测试课程的,您让孩子来试试看吧?;
|
| 158 |
+
3.
|
| 159 |
+
问: 公司/上课地点
|
| 160 |
+
答: 我们专业老师在线授课哦,宝贝在电脑、平板都可以学习,在家就可以上课,很方便的。;
|
| 161 |
+
4.
|
| 162 |
+
问: 是否机器人
|
| 163 |
+
答: 哎,家长,我是LingoAce的约课老师呢,今天联系您也是想邀请您免费体验我们的全英文授课的新加坡数学试听课,您看要不要让孩子来体验一下呢?;
|
| 164 |
+
5.
|
| 165 |
+
问: 考虑商量
|
| 166 |
+
答: 家长,我们课程更新了,现在是针对北美、澳洲、欧洲小学阶段的数学培优课程,新加坡数学注重扎实基础和应用能力的提升,可以提升孩子思维能力。现在有免费试听的机会,我先帮您约上,您看看是否适合孩子,可以吗;
|
| 167 |
+
6.
|
| 168 |
+
问:客户问AI是否听得到
|
| 169 |
+
答:在听的,您接着说;
|
| 170 |
+
7.
|
| 171 |
+
问:赞美
|
| 172 |
+
答:感谢感谢,谢谢您。那您看这次的活动可以给孩子报个名来体验下吗?;
|
| 173 |
+
8.
|
| 174 |
+
问:听不清楚AI说话
|
| 175 |
+
答:现在您能听清楚吗?;
|
| 176 |
+
9.
|
| 177 |
+
问:课程教材
|
| 178 |
+
答:只需要小朋友有电脑或者平板就可以体验了;
|
| 179 |
+
10.
|
| 180 |
+
问:怎么称呼
|
| 181 |
+
答:家长您叫我静静老师就可以。;
|
| 182 |
+
11.
|
| 183 |
+
问:干什么的
|
| 184 |
+
答:是这样的家长,我是LingoAce的老师,您还有一节免费试听的权益没有领取,是有真人老师一对一直播上课的,您让孩子来体验看看吧?;
|
| 185 |
+
12.
|
| 186 |
+
问:是不是诈骗
|
| 187 |
+
答:家长您多虑了,我们是LingoAce的老师,这次也是根据您之前注册的信息来联系您的,您也可以登录我们app看一下的;
|
| 188 |
+
13.
|
| 189 |
+
问:平台名称
|
| 190 |
+
答:我们是LingoAce,您之前有在我们平台给宝贝了解过数学课程的哈。;
|
| 191 |
+
14.
|
| 192 |
+
问:联系电话
|
| 193 |
+
答:我们这个电话是不支持回拨的,您如果想要了解更多课程内容,稍后我们专属顾问会主动与您取得联系的;
|
| 194 |
+
15.
|
| 195 |
+
问:询问线下
|
| 196 |
+
答:我们是线上授课的方式,线下是没有的,您可以先来体验下,有什么问题可以咨询我们专属顾问哈;
|
| 197 |
+
16.
|
| 198 |
+
问:调戏
|
| 199 |
+
答:非常抱歉呢,跟工作不相关的问题上班时间我们是不允许聊的,咱们还是回归到宝贝课程的学习上来吧。;
|
| 200 |
+
17.
|
| 201 |
+
问:下载软件/APP
|
| 202 |
+
答:稍后课程顾问会联系您,告诉您详细的操作流程的。;
|
| 203 |
+
18.
|
| 204 |
+
问:老师资质
|
| 205 |
+
答:老师资质这块您是可以放心的,我们老师都是毕业于全球知名院校的专业科班老师,也都是经过我们层层筛选才能上任的呢;
|
| 206 |
+
19.
|
| 207 |
+
问:优惠活动
|
| 208 |
+
答:这次帮您申请的是免费名额,您可以先体验一下,关于正式课程的优惠,我们专业的专属顾问稍后会给您全面介绍的;
|
| 209 |
+
20.
|
| 210 |
+
问:课程内容
|
| 211 |
+
答:稍后会有课程顾问联系您,给您详细介绍的。;
|
| 212 |
+
21.
|
| 213 |
+
问:考虑商量
|
| 214 |
+
答: 家长,我们课程更新了,现在是针对北美、澳洲、欧洲小学阶段的数学培优课程,新加坡数学注重扎实基础和应用能力的提升,可以提升孩子思维能力。现在有免费试听的机会,我先帮您约上,您看看是否适合孩子,可以吗;
|
| 215 |
+
22.
|
| 216 |
+
问:已经报班
|
| 217 |
+
答:好的家长,您多考虑一下,我们的课程也是最新升级了,可以让孩子参与数学培优课程,提升孩子思维能力。这次给您争取的免费名额,您先让孩子体验一下,您看可以吗?;
|
| 218 |
+
23.
|
| 219 |
+
问:适合多大孩子学习
|
| 220 |
+
答:我们的课程,主要针对的是3-18岁左右的孩子学习数学的哦。;
|
| 221 |
+
24.
|
| 222 |
+
问:一节课时长
|
| 223 |
+
答:我们有25分钟和55分钟的课程,可以根据小朋友的实际情况进行灵活选择的;
|
| 224 |
+
25.
|
| 225 |
+
问:不在某地
|
| 226 |
+
答:我们是真人老师在线一对一授课,更有趣味性,而且孩子在电脑、苹果Ipad都可以学习,您随时都可以预约上课,在家就能学,很方便的。;
|
| 227 |
+
26.
|
| 228 |
+
问:优势及区别
|
| 229 |
+
答:哎,我们的课程是经过教育专家们精心编排过的,非常适合各个年龄段孩子的认知特点。而且是针对北美、澳洲、欧洲小学阶段的数学培优课程,新加坡数学注重扎实基础和应用能力的提升,可以提升孩子思维能力。您可以让孩子来体验看看的;
|
| 230 |
+
27.
|
| 231 |
+
问:有没有其他课程
|
| 232 |
+
答:有的,比如国际数学竞赛类的,袋鼠竞赛,AMC8 等等课程,如果您感兴趣,我们可以让老师回电给您介绍一下;
|
| 233 |
+
28.
|
| 234 |
+
问:家长旁听
|
| 235 |
+
答:旁听可以的,第一次上课我们也希望您可以陪同,可以更好的了解我们的课程,但是不建议您对孩子有太多���点呢~;
|
| 236 |
+
29.
|
| 237 |
+
问:正式报名详情
|
| 238 |
+
答:您先和小朋友一起来体验哈,试听结束后,您觉得小朋友挺喜欢,具体的费用您可以到时候直接咨询我们老师哈;
|
| 239 |
+
30.
|
| 240 |
+
问:试听之后怎么收费
|
| 241 |
+
答:这个后面会有专属顾问联系您,跟您说一下这个问题的;
|
| 242 |
+
31.
|
| 243 |
+
问:判断孩子基础
|
| 244 |
+
答:我们这边有很多零基础的小朋友,而且我们的老师教学经验很丰富,注重扎实基础和应用能力的提升。这个您不用担心的,可以先试试,好吧?;
|
| 245 |
+
32.
|
| 246 |
+
问:活动时间限制
|
| 247 |
+
答:这个是我们给宝贝争取的免费名额,现在登记一下,稍后就会有课程老师来跟您约具体时间的。;
|
| 248 |
+
33.
|
| 249 |
+
问:直播还是录播
|
| 250 |
+
答:我们是真人老师授课哦,直播上课的,有互动有交流。;
|
| 251 |
+
34.
|
| 252 |
+
问:体验过了
|
| 253 |
+
答:哎,是因为咱们课程又更新了,我们课程更新了,现在是针对北美、澳洲、欧洲小学阶段的数学培优课程,新加坡数学注重扎实基础和应用能力的提升,可以提升孩子思维能力。我们给您争取了一个免费名额,您让孩子再来试试看吧?;
|
| 254 |
+
35.
|
| 255 |
+
问:线上不考虑
|
| 256 |
+
答:哎,家长,线上很方便的,孩子不用线下到处跑,在家就能学习,而且我们是一对一授课的模式,可以根据孩子基础有针对性地开展教学计划,而且我们课后还会生成专属的学习报告呢;
|
| 257 |
+
36.
|
| 258 |
+
问:上课方式
|
| 259 |
+
答:我们是在线的一对一授课的形式,真人老师互动,现在可以免费体验,在电脑、苹果Ipad都可以学习,您随时都可以预约上课,在家就能学,很方便的。 ;
|
| 260 |
+
37.
|
| 261 |
+
问:固定老师
|
| 262 |
+
答:是的,正式课我们是固定老师授课,可以熟悉孩子情况,熟悉学习进度,对孩子做到更针对的教学呢。;
|
| 263 |
+
38.
|
| 264 |
+
问:课程是几节课
|
| 265 |
+
答:试听课是一节哦,现在邀请孩子免费体验。您看孩子有需要的话,稍后我让专属的顾问老师来帮您预约课程吧;
|
| 266 |
+
39.
|
| 267 |
+
问:老师是兼职的还是全职的
|
| 268 |
+
答:我们的老师都是经过公司内部的培训加选拔制度选出来的,老师资质这块您是可以放心的;
|
| 269 |
+
40.
|
| 270 |
+
问:报了线下班
|
| 271 |
+
答:那您正好可以利用这节体验检测下孩子目前的学习效果,我们可以全方位分析孩子哪一块比较薄弱,有针对性的提供学习建议。而且每家机构教学方法都不太一样,您可以先体验一下嘛。;
|
| 272 |
+
41.
|
| 273 |
+
问:孩子不想学
|
| 274 |
+
答:家长,咱们孩子早晚都要接触数学的,这次安排的课程老师教学经验很丰富,会根据孩子情况调整授课方式的,很多小朋友反映说很有趣,您可以让孩子试试,也让我们老师给您一些建议,好吗?;
|
| 275 |
+
范例:
|
| 276 |
+
客户:“hi”
|
| 277 |
+
客服:“Hello,您好”
|
| 278 |
+
客户:“什么事”
|
| 279 |
+
客服:“诶您好,我是LingoAce在线学中文的,先前一段时间咱们孩子有在我们这里学习,您还记得吧?”
|
| 280 |
+
客户:“你是机器人吗?”
|
| 281 |
+
客服:“哎,家长,我是LingoAce的约课老师呢,今天联系您也是想邀请您免费体验我们的全英文授课的新加坡数学试听课,您看要不要让孩子来体验一下呢?”
|
| 282 |
+
<约束>
|
| 283 |
+
"""
|
| 284 |
+
|
| 285 |
+
|
| 286 |
+
async def main():
|
| 287 |
+
args = get_args()
|
| 288 |
+
|
| 289 |
+
gemini = GeminiNativeAudio(
|
| 290 |
+
gemini_api_key=args.gemini_api_key,
|
| 291 |
+
model_name=args.model_name,
|
| 292 |
+
system_instruction=INSTRUCTION,
|
| 293 |
+
)
|
| 294 |
+
await gemini.run()
|
| 295 |
+
return
|
| 296 |
+
|
| 297 |
+
|
| 298 |
+
if __name__ == "__main__":
|
| 299 |
+
asyncio.run(main())
|
examples/quickstarts/Get_started_LiveAPI_NativeAudio.py
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# -*- coding: utf-8 -*-
|
| 2 |
+
# Copyright 2025 Google LLC
|
| 3 |
+
#
|
| 4 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 5 |
+
# you may not use this file except in compliance with the License.
|
| 6 |
+
# You may obtain a copy of the License at
|
| 7 |
+
#
|
| 8 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 9 |
+
#
|
| 10 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 11 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 12 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 13 |
+
# See the License for the specific language governing permissions and
|
| 14 |
+
# limitations under the License.
|
| 15 |
+
|
| 16 |
+
"""
|
| 17 |
+
https://ai.google.dev/gemini-api/docs/live?hl=zh-cn#python_1
|
| 18 |
+
https://github.com/google-gemini/cookbook/blob/main/quickstarts/Get_started_LiveAPI_NativeAudio.py
|
| 19 |
+
|
| 20 |
+
## Setup
|
| 21 |
+
|
| 22 |
+
To install the dependencies for this script, run:
|
| 23 |
+
|
| 24 |
+
```
|
| 25 |
+
brew install portaudio
|
| 26 |
+
pip install -U google-genai pyaudio
|
| 27 |
+
```
|
| 28 |
+
|
| 29 |
+
## API key
|
| 30 |
+
|
| 31 |
+
Ensure the `GOOGLE_API_KEY` environment variable is set to the api-key
|
| 32 |
+
you obtained from Google AI Studio.
|
| 33 |
+
|
| 34 |
+
## Run
|
| 35 |
+
|
| 36 |
+
To run the script:
|
| 37 |
+
|
| 38 |
+
```
|
| 39 |
+
python Get_started_LiveAPI_NativeAudio.py
|
| 40 |
+
```
|
| 41 |
+
|
| 42 |
+
Start talking to Gemini
|
| 43 |
+
"""
|
| 44 |
+
import asyncio
|
| 45 |
+
import os
|
| 46 |
+
import sys
|
| 47 |
+
import traceback
|
| 48 |
+
|
| 49 |
+
import pyaudio
|
| 50 |
+
|
| 51 |
+
from google import genai
|
| 52 |
+
|
| 53 |
+
from project_settings import environment
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
if sys.version_info < (3, 11, 0):
|
| 57 |
+
import taskgroup, exceptiongroup
|
| 58 |
+
|
| 59 |
+
asyncio.TaskGroup = taskgroup.TaskGroup
|
| 60 |
+
asyncio.ExceptionGroup = exceptiongroup.ExceptionGroup
|
| 61 |
+
|
| 62 |
+
FORMAT = pyaudio.paInt16
|
| 63 |
+
CHANNELS = 1
|
| 64 |
+
SEND_SAMPLE_RATE = 16000
|
| 65 |
+
RECEIVE_SAMPLE_RATE = 24000
|
| 66 |
+
CHUNK_SIZE = 1024
|
| 67 |
+
|
| 68 |
+
pya = pyaudio.PyAudio()
|
| 69 |
+
|
| 70 |
+
GOOGLE_API_KEY = environment.get("GEMINI_API_KEY")
|
| 71 |
+
os.environ["GOOGLE_API_KEY"] = GOOGLE_API_KEY
|
| 72 |
+
client = genai.Client() # GOOGLE_API_KEY must be set as env variable
|
| 73 |
+
|
| 74 |
+
MODEL = "gemini-2.5-flash-preview-native-audio-dialog"
|
| 75 |
+
CONFIG = {"response_modalities": ["AUDIO"]}
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
class AudioLoop:
|
| 79 |
+
def __init__(self):
|
| 80 |
+
self.audio_in_queue = None
|
| 81 |
+
self.out_queue = None
|
| 82 |
+
|
| 83 |
+
self.session = None
|
| 84 |
+
|
| 85 |
+
self.audio_stream = None
|
| 86 |
+
|
| 87 |
+
self.receive_audio_task = None
|
| 88 |
+
self.play_audio_task = None
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
async def listen_audio(self):
|
| 92 |
+
mic_info = pya.get_default_input_device_info()
|
| 93 |
+
self.audio_stream = await asyncio.to_thread(
|
| 94 |
+
pya.open,
|
| 95 |
+
format=FORMAT,
|
| 96 |
+
channels=CHANNELS,
|
| 97 |
+
rate=SEND_SAMPLE_RATE,
|
| 98 |
+
input=True,
|
| 99 |
+
input_device_index=mic_info["index"],
|
| 100 |
+
frames_per_buffer=CHUNK_SIZE,
|
| 101 |
+
)
|
| 102 |
+
if __debug__:
|
| 103 |
+
kwargs = {"exception_on_overflow": False}
|
| 104 |
+
else:
|
| 105 |
+
kwargs = {}
|
| 106 |
+
while True:
|
| 107 |
+
data = await asyncio.to_thread(self.audio_stream.read, CHUNK_SIZE, **kwargs)
|
| 108 |
+
await self.out_queue.put({"data": data, "mime_type": "audio/pcm"})
|
| 109 |
+
|
| 110 |
+
async def send_realtime(self):
|
| 111 |
+
while True:
|
| 112 |
+
msg = await self.out_queue.get()
|
| 113 |
+
await self.session.send_realtime_input(audio=msg)
|
| 114 |
+
|
| 115 |
+
async def receive_audio(self):
|
| 116 |
+
"Background task to reads from the websocket and write pcm chunks to the output queue"
|
| 117 |
+
while True:
|
| 118 |
+
turn = self.session.receive()
|
| 119 |
+
async for response in turn:
|
| 120 |
+
if data := response.data:
|
| 121 |
+
self.audio_in_queue.put_nowait(data)
|
| 122 |
+
continue
|
| 123 |
+
if text := response.text:
|
| 124 |
+
print(text, end="")
|
| 125 |
+
|
| 126 |
+
# If you interrupt the model, it sends a turn_complete.
|
| 127 |
+
# For interruptions to work, we need to stop playback.
|
| 128 |
+
# So empty out the audio queue because it may have loaded
|
| 129 |
+
# much more audio than has played yet.
|
| 130 |
+
while not self.audio_in_queue.empty():
|
| 131 |
+
self.audio_in_queue.get_nowait()
|
| 132 |
+
|
| 133 |
+
async def play_audio(self):
|
| 134 |
+
stream = await asyncio.to_thread(
|
| 135 |
+
pya.open,
|
| 136 |
+
format=FORMAT,
|
| 137 |
+
channels=CHANNELS,
|
| 138 |
+
rate=RECEIVE_SAMPLE_RATE,
|
| 139 |
+
output=True,
|
| 140 |
+
)
|
| 141 |
+
while True:
|
| 142 |
+
bytestream = await self.audio_in_queue.get()
|
| 143 |
+
await asyncio.to_thread(stream.write, bytestream)
|
| 144 |
+
|
| 145 |
+
async def run(self):
|
| 146 |
+
try:
|
| 147 |
+
async with (
|
| 148 |
+
client.aio.live.connect(model=MODEL, config=CONFIG) as session,
|
| 149 |
+
asyncio.TaskGroup() as tg,
|
| 150 |
+
):
|
| 151 |
+
self.session = session
|
| 152 |
+
|
| 153 |
+
self.audio_in_queue = asyncio.Queue()
|
| 154 |
+
self.out_queue = asyncio.Queue(maxsize=5)
|
| 155 |
+
|
| 156 |
+
tg.create_task(self.send_realtime())
|
| 157 |
+
tg.create_task(self.listen_audio())
|
| 158 |
+
tg.create_task(self.receive_audio())
|
| 159 |
+
tg.create_task(self.play_audio())
|
| 160 |
+
except asyncio.CancelledError:
|
| 161 |
+
pass
|
| 162 |
+
except ExceptionGroup as EG:
|
| 163 |
+
if self.audio_stream:
|
| 164 |
+
self.audio_stream.close()
|
| 165 |
+
traceback.print_exception(EG)
|
| 166 |
+
|
| 167 |
+
|
| 168 |
+
if __name__ == "__main__":
|
| 169 |
+
loop = AudioLoop()
|
| 170 |
+
asyncio.run(loop.run())
|
examples/qwen/quick_start/main.py
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# -- coding: utf-8 --
|
| 2 |
+
"""
|
| 3 |
+
https://help.aliyun.com/zh/model-studio/realtime#1234095db03g3
|
| 4 |
+
"""
|
| 5 |
+
import os, time, base64, asyncio
|
| 6 |
+
from omni_realtime_client import OmniRealtimeClient, TurnDetectionMode
|
| 7 |
+
import pyaudio
|
| 8 |
+
import queue
|
| 9 |
+
import threading
|
| 10 |
+
|
| 11 |
+
from project_settings import environment
|
| 12 |
+
DASHSCOPE_API_KEY = environment.get("QWEN_API_KEY")
|
| 13 |
+
os.environ["DASHSCOPE_API_KEY"] = DASHSCOPE_API_KEY
|
| 14 |
+
|
| 15 |
+
# 创建一个全局音频队列和播放线程
|
| 16 |
+
audio_queue = queue.Queue()
|
| 17 |
+
audio_player = None
|
| 18 |
+
|
| 19 |
+
# 初始化PyAudio
|
| 20 |
+
p = pyaudio.PyAudio()
|
| 21 |
+
RATE = 16000 # 采样率 16kHz
|
| 22 |
+
CHUNK = 3200 # 每个音频块的大小
|
| 23 |
+
FORMAT = pyaudio.paInt16 # 16位PCM格式
|
| 24 |
+
CHANNELS = 1 # 单声道
|
| 25 |
+
|
| 26 |
+
def audio_player_thread():
|
| 27 |
+
"""后台线程用于播放音频数据"""
|
| 28 |
+
stream = p.open(format=FORMAT,
|
| 29 |
+
channels=CHANNELS,
|
| 30 |
+
rate=24000,
|
| 31 |
+
output=True,
|
| 32 |
+
frames_per_buffer=CHUNK)
|
| 33 |
+
|
| 34 |
+
try:
|
| 35 |
+
while True:
|
| 36 |
+
try:
|
| 37 |
+
# 从队列获取音频数据
|
| 38 |
+
audio_data = audio_queue.get(block=True, timeout=0.5)
|
| 39 |
+
if audio_data is None: # 结束信号
|
| 40 |
+
break
|
| 41 |
+
# 播放音频数据
|
| 42 |
+
stream.write(audio_data)
|
| 43 |
+
audio_queue.task_done()
|
| 44 |
+
except queue.Empty:
|
| 45 |
+
# 如果队列为空,继续等待
|
| 46 |
+
continue
|
| 47 |
+
finally:
|
| 48 |
+
# 清理
|
| 49 |
+
stream.stop_stream()
|
| 50 |
+
stream.close()
|
| 51 |
+
|
| 52 |
+
def start_audio_player():
|
| 53 |
+
"""启动音频播放线程"""
|
| 54 |
+
global audio_player
|
| 55 |
+
if audio_player is None or not audio_player.is_alive():
|
| 56 |
+
audio_player = threading.Thread(target=audio_player_thread, daemon=True)
|
| 57 |
+
audio_player.start()
|
| 58 |
+
|
| 59 |
+
def handle_audio_data(audio_data):
|
| 60 |
+
"""处理接收到的音频数据"""
|
| 61 |
+
# 打印接收到的音频数据长度(调试用)
|
| 62 |
+
print(f"\n接收到音频数据: {len(audio_data)} 字节")
|
| 63 |
+
# 将音频数据放入队列
|
| 64 |
+
audio_queue.put(audio_data)
|
| 65 |
+
|
| 66 |
+
async def start_microphone_streaming(client: OmniRealtimeClient):
|
| 67 |
+
CHUNK = 3200
|
| 68 |
+
FORMAT = pyaudio.paInt16
|
| 69 |
+
CHANNELS = 1
|
| 70 |
+
RATE = 16000
|
| 71 |
+
|
| 72 |
+
p = pyaudio.PyAudio()
|
| 73 |
+
stream = p.open(format=FORMAT,
|
| 74 |
+
channels=CHANNELS,
|
| 75 |
+
rate=RATE,
|
| 76 |
+
input=True,
|
| 77 |
+
frames_per_buffer=CHUNK)
|
| 78 |
+
|
| 79 |
+
try:
|
| 80 |
+
print("开始录音,请讲话...")
|
| 81 |
+
while True:
|
| 82 |
+
audio_data = stream.read(CHUNK)
|
| 83 |
+
encoded_data = base64.b64encode(audio_data).decode('utf-8')
|
| 84 |
+
|
| 85 |
+
eventd = {
|
| 86 |
+
"event_id": "event_" + str(int(time.time() * 1000)),
|
| 87 |
+
"type": "input_audio_buffer.append",
|
| 88 |
+
"audio": encoded_data
|
| 89 |
+
}
|
| 90 |
+
await client.send_event(eventd)
|
| 91 |
+
|
| 92 |
+
# 保持较短的等待时间以模拟实时交互
|
| 93 |
+
await asyncio.sleep(0.05)
|
| 94 |
+
finally:
|
| 95 |
+
stream.stop_stream()
|
| 96 |
+
stream.close()
|
| 97 |
+
p.terminate()
|
| 98 |
+
|
| 99 |
+
async def main():
|
| 100 |
+
# 启动音频播放线程
|
| 101 |
+
start_audio_player()
|
| 102 |
+
|
| 103 |
+
realtime_client = OmniRealtimeClient(
|
| 104 |
+
base_url="wss://dashscope.aliyuncs.com/api-ws/v1/realtime",
|
| 105 |
+
api_key=os.environ.get("DASHSCOPE_API_KEY"),
|
| 106 |
+
model="qwen-omni-turbo-realtime-2025-05-08",
|
| 107 |
+
voice="Chelsie",
|
| 108 |
+
on_text_delta=lambda text: print(f"\nAssistant: {text}", end="", flush=True),
|
| 109 |
+
on_audio_delta=handle_audio_data,
|
| 110 |
+
turn_detection_mode=TurnDetectionMode.SERVER_VAD
|
| 111 |
+
)
|
| 112 |
+
|
| 113 |
+
try:
|
| 114 |
+
await realtime_client.connect()
|
| 115 |
+
# 启动消息处理和麦克风录音
|
| 116 |
+
message_handler = asyncio.create_task(realtime_client.handle_messages())
|
| 117 |
+
streaming_task = asyncio.create_task(start_microphone_streaming(realtime_client))
|
| 118 |
+
|
| 119 |
+
while True:
|
| 120 |
+
await asyncio.Queue().get()
|
| 121 |
+
except Exception as e:
|
| 122 |
+
print(f"Error: {e}")
|
| 123 |
+
finally:
|
| 124 |
+
# 结束音频播放线程
|
| 125 |
+
audio_queue.put(None)
|
| 126 |
+
if audio_player:
|
| 127 |
+
audio_player.join(timeout=1)
|
| 128 |
+
await realtime_client.close()
|
| 129 |
+
p.terminate()
|
| 130 |
+
|
| 131 |
+
if __name__ == "__main__":
|
| 132 |
+
# Install required packages:
|
| 133 |
+
asyncio.run(main())
|
examples/qwen/quick_start/omni_realtime_client.py
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# -- coding: utf-8 --
|
| 2 |
+
|
| 3 |
+
import asyncio
|
| 4 |
+
import websockets
|
| 5 |
+
import json
|
| 6 |
+
import base64
|
| 7 |
+
import os
|
| 8 |
+
import time
|
| 9 |
+
|
| 10 |
+
from typing import Optional, Callable, List, Dict, Any
|
| 11 |
+
from enum import Enum
|
| 12 |
+
|
| 13 |
+
class TurnDetectionMode(Enum):
|
| 14 |
+
SERVER_VAD = "server_vad"
|
| 15 |
+
MANUAL = "manual"
|
| 16 |
+
|
| 17 |
+
class OmniRealtimeClient:
|
| 18 |
+
"""
|
| 19 |
+
A demo client for interacting with the Omni Realtime API.
|
| 20 |
+
|
| 21 |
+
This class provides methods to connect to the Realtime API, send text and audio data,
|
| 22 |
+
handle responses, and manage the WebSocket connection.
|
| 23 |
+
|
| 24 |
+
Attributes:
|
| 25 |
+
base_url (str):
|
| 26 |
+
The base URL for the Realtime API.
|
| 27 |
+
api_key (str):
|
| 28 |
+
The API key for authentication.
|
| 29 |
+
model (str):
|
| 30 |
+
Omni model to use for chat.
|
| 31 |
+
voice (str):
|
| 32 |
+
The voice to use for audio output.
|
| 33 |
+
turn_detection_mode (TurnDetectionMode):
|
| 34 |
+
The mode for turn detection.
|
| 35 |
+
on_text_delta (Callable[[str], None]):
|
| 36 |
+
Callback for text delta events.
|
| 37 |
+
Takes in a string and returns nothing.
|
| 38 |
+
on_audio_delta (Callable[[bytes], None]):
|
| 39 |
+
Callback for audio delta events.
|
| 40 |
+
Takes in bytes and returns nothing.
|
| 41 |
+
on_input_transcript (Callable[[str], None]):
|
| 42 |
+
Callback for input transcript events.
|
| 43 |
+
Takes in a string and returns nothing.
|
| 44 |
+
on_interrupt (Callable[[], None]):
|
| 45 |
+
Callback for user interrupt events, should be used to stop audio playback.
|
| 46 |
+
on_output_transcript (Callable[[str], None]):
|
| 47 |
+
Callback for output transcript events.
|
| 48 |
+
Takes in a string and returns nothing.
|
| 49 |
+
extra_event_handlers (Dict[str, Callable[[Dict[str, Any]], None]]):
|
| 50 |
+
Additional event handlers.
|
| 51 |
+
Is a mapping of event names to functions that process the event payload.
|
| 52 |
+
"""
|
| 53 |
+
def __init__(
|
| 54 |
+
self,
|
| 55 |
+
base_url,
|
| 56 |
+
api_key: str,
|
| 57 |
+
model: str = "",
|
| 58 |
+
voice: str = "Chelsie",
|
| 59 |
+
turn_detection_mode: TurnDetectionMode = TurnDetectionMode.MANUAL,
|
| 60 |
+
on_text_delta: Optional[Callable[[str], None]] = None,
|
| 61 |
+
on_audio_delta: Optional[Callable[[bytes], None]] = None,
|
| 62 |
+
on_interrupt: Optional[Callable[[], None]] = None,
|
| 63 |
+
on_input_transcript: Optional[Callable[[str], None]] = None,
|
| 64 |
+
on_output_transcript: Optional[Callable[[str], None]] = None,
|
| 65 |
+
extra_event_handlers: Optional[Dict[str, Callable[[Dict[str, Any]], None]]] = None
|
| 66 |
+
):
|
| 67 |
+
self.base_url = base_url
|
| 68 |
+
self.api_key = api_key
|
| 69 |
+
self.model = model
|
| 70 |
+
self.voice = voice
|
| 71 |
+
self.ws = None
|
| 72 |
+
self.on_text_delta = on_text_delta
|
| 73 |
+
self.on_audio_delta = on_audio_delta
|
| 74 |
+
self.on_interrupt = on_interrupt
|
| 75 |
+
self.on_input_transcript = on_input_transcript
|
| 76 |
+
self.on_output_transcript = on_output_transcript
|
| 77 |
+
self.turn_detection_mode = turn_detection_mode
|
| 78 |
+
self.extra_event_handlers = extra_event_handlers or {}
|
| 79 |
+
|
| 80 |
+
# Track current response state
|
| 81 |
+
self._current_response_id = None
|
| 82 |
+
self._current_item_id = None
|
| 83 |
+
self._is_responding = False
|
| 84 |
+
# Track printing state for input and output transcripts
|
| 85 |
+
self._print_input_transcript = False
|
| 86 |
+
self._output_transcript_buffer = ""
|
| 87 |
+
|
| 88 |
+
async def connect(self) -> None:
|
| 89 |
+
"""Establish WebSocket connection with the Realtime API."""
|
| 90 |
+
url = f"{self.base_url}?model={self.model}"
|
| 91 |
+
headers = {
|
| 92 |
+
"Authorization": f"Bearer {self.api_key}"
|
| 93 |
+
}
|
| 94 |
+
print(f"url: {url}, headers: {headers}")
|
| 95 |
+
|
| 96 |
+
self.ws = await websockets.connect(url, additional_headers=headers)
|
| 97 |
+
|
| 98 |
+
# Set up default session configuration
|
| 99 |
+
if self.turn_detection_mode == TurnDetectionMode.MANUAL:
|
| 100 |
+
await self.update_session({
|
| 101 |
+
"modalities": ["text", "audio"],
|
| 102 |
+
"voice": self.voice,
|
| 103 |
+
"input_audio_format": "pcm16",
|
| 104 |
+
"output_audio_format": "pcm16",
|
| 105 |
+
"input_audio_transcription": {
|
| 106 |
+
"model": "gummy-realtime-v1"
|
| 107 |
+
},
|
| 108 |
+
"turn_detection" : None
|
| 109 |
+
})
|
| 110 |
+
elif self.turn_detection_mode == TurnDetectionMode.SERVER_VAD:
|
| 111 |
+
await self.update_session({
|
| 112 |
+
"modalities": ["text", "audio"],
|
| 113 |
+
"voice": self.voice,
|
| 114 |
+
"input_audio_format": "pcm16",
|
| 115 |
+
"output_audio_format": "pcm16",
|
| 116 |
+
"input_audio_transcription": {
|
| 117 |
+
"model": "gummy-realtime-v1"
|
| 118 |
+
},
|
| 119 |
+
"turn_detection": {
|
| 120 |
+
"type": "server_vad",
|
| 121 |
+
"threshold": 0.1,
|
| 122 |
+
"prefix_padding_ms": 500,
|
| 123 |
+
"silence_duration_ms": 900
|
| 124 |
+
}
|
| 125 |
+
})
|
| 126 |
+
else:
|
| 127 |
+
raise ValueError(f"Invalid turn detection mode: {self.turn_detection_mode}")
|
| 128 |
+
|
| 129 |
+
async def send_event(self, event) -> None:
|
| 130 |
+
event['event_id'] = "event_" + str(int(time.time() * 1000))
|
| 131 |
+
print(f" Send event: type={event['type']}, event_id={event['event_id']}")
|
| 132 |
+
await self.ws.send(json.dumps(event))
|
| 133 |
+
|
| 134 |
+
async def update_session(self, config: Dict[str, Any]) -> None:
|
| 135 |
+
"""Update session configuration."""
|
| 136 |
+
event = {
|
| 137 |
+
"type": "session.update",
|
| 138 |
+
"session": config
|
| 139 |
+
}
|
| 140 |
+
print("update session: ", event)
|
| 141 |
+
await self.send_event(event)
|
| 142 |
+
|
| 143 |
+
async def stream_audio(self, audio_chunk: bytes) -> None:
|
| 144 |
+
"""Stream raw audio data to the API."""
|
| 145 |
+
# only support 16bit 16kHz mono pcm
|
| 146 |
+
audio_b64 = base64.b64encode(audio_chunk).decode()
|
| 147 |
+
|
| 148 |
+
append_event = {
|
| 149 |
+
"type": "input_audio_buffer.append",
|
| 150 |
+
"audio": audio_b64
|
| 151 |
+
}
|
| 152 |
+
await self.send_event(json.dumps(append_event))
|
| 153 |
+
|
| 154 |
+
async def create_response(self) -> None:
|
| 155 |
+
"""Request a response from the API. Needed when using manual mode."""
|
| 156 |
+
event = {
|
| 157 |
+
"type": "response.create",
|
| 158 |
+
"response": {
|
| 159 |
+
"instructions": "你是Tom,一个美国的导购,负责售卖手机、电视",
|
| 160 |
+
"modalities": ["text", "audio"]
|
| 161 |
+
}
|
| 162 |
+
}
|
| 163 |
+
print("create response: ", event)
|
| 164 |
+
await self.send_event(event)
|
| 165 |
+
|
| 166 |
+
async def cancel_response(self) -> None:
|
| 167 |
+
"""Cancel the current response."""
|
| 168 |
+
event = {
|
| 169 |
+
"type": "response.cancel"
|
| 170 |
+
}
|
| 171 |
+
await self.send_event(event)
|
| 172 |
+
|
| 173 |
+
async def handle_interruption(self):
|
| 174 |
+
"""Handle user interruption of the current response."""
|
| 175 |
+
if not self._is_responding:
|
| 176 |
+
return
|
| 177 |
+
|
| 178 |
+
print(" Handling interruption")
|
| 179 |
+
|
| 180 |
+
# 1. Cancel the current response
|
| 181 |
+
if self._current_response_id:
|
| 182 |
+
await self.cancel_response()
|
| 183 |
+
|
| 184 |
+
self._is_responding = False
|
| 185 |
+
self._current_response_id = None
|
| 186 |
+
self._current_item_id = None
|
| 187 |
+
|
| 188 |
+
async def handle_messages(self) -> None:
|
| 189 |
+
try:
|
| 190 |
+
async for message in self.ws:
|
| 191 |
+
event = json.loads(message)
|
| 192 |
+
event_type = event.get("type")
|
| 193 |
+
|
| 194 |
+
if event_type != "response.audio.delta":
|
| 195 |
+
print(" event: ", event)
|
| 196 |
+
else:
|
| 197 |
+
print(" event_type: ", event_type)
|
| 198 |
+
|
| 199 |
+
if event_type == "error":
|
| 200 |
+
print(" Error: ", event['error'])
|
| 201 |
+
continue
|
| 202 |
+
elif event_type == "response.created":
|
| 203 |
+
self._current_response_id = event.get("response", {}).get("id")
|
| 204 |
+
self._is_responding = True
|
| 205 |
+
elif event_type == "response.output_item.added":
|
| 206 |
+
self._current_item_id = event.get("item", {}).get("id")
|
| 207 |
+
elif event_type == "response.done":
|
| 208 |
+
self._is_responding = False
|
| 209 |
+
self._current_response_id = None
|
| 210 |
+
self._current_item_id = None
|
| 211 |
+
# Handle interruptions
|
| 212 |
+
elif event_type == "input_audio_buffer.speech_started":
|
| 213 |
+
print(" Speech detected")
|
| 214 |
+
if self._is_responding:
|
| 215 |
+
print(" Handling interruption")
|
| 216 |
+
await self.handle_interruption()
|
| 217 |
+
|
| 218 |
+
if self.on_interrupt:
|
| 219 |
+
print(" Handling on_interrupt, stop playback")
|
| 220 |
+
self.on_interrupt()
|
| 221 |
+
elif event_type == "input_audio_buffer.speech_stopped":
|
| 222 |
+
print(" Speech ended")
|
| 223 |
+
# Handle normal response events
|
| 224 |
+
elif event_type == "response.text.delta":
|
| 225 |
+
if self.on_text_delta:
|
| 226 |
+
self.on_text_delta(event["delta"])
|
| 227 |
+
elif event_type == "response.audio.delta":
|
| 228 |
+
if self.on_audio_delta:
|
| 229 |
+
audio_bytes = base64.b64decode(event["delta"])
|
| 230 |
+
self.on_audio_delta(audio_bytes)
|
| 231 |
+
elif event_type == "conversation.item.input_audio_transcription.completed":
|
| 232 |
+
transcript = event.get("transcript", "")
|
| 233 |
+
if self.on_input_transcript:
|
| 234 |
+
await asyncio.to_thread(self.on_input_transcript,transcript)
|
| 235 |
+
self._print_input_transcript = True
|
| 236 |
+
elif event_type == "response.audio_transcript.delta":
|
| 237 |
+
if self.on_output_transcript:
|
| 238 |
+
delta = event.get("delta", "")
|
| 239 |
+
if not self._print_input_transcript:
|
| 240 |
+
self._output_transcript_buffer += delta
|
| 241 |
+
else:
|
| 242 |
+
if self._output_transcript_buffer:
|
| 243 |
+
await asyncio.to_thread(self.on_output_transcript,self._output_transcript_buffer)
|
| 244 |
+
self._output_transcript_buffer = ""
|
| 245 |
+
await asyncio.to_thread(self.on_output_transcript,delta)
|
| 246 |
+
elif event_type == "response.audio_transcript.done":
|
| 247 |
+
self._print_input_transcript = False
|
| 248 |
+
elif event_type in self.extra_event_handlers:
|
| 249 |
+
self.extra_event_handlers[event_type](event)
|
| 250 |
+
|
| 251 |
+
except websockets.exceptions.ConnectionClosed:
|
| 252 |
+
print(" Connection closed")
|
| 253 |
+
except Exception as e:
|
| 254 |
+
print(" Error in message handling: ", str(e))
|
| 255 |
+
|
| 256 |
+
async def close(self) -> None:
|
| 257 |
+
"""Close the WebSocket connection."""
|
| 258 |
+
if self.ws:
|
| 259 |
+
await self.ws.close()
|
examples/qwen/qwen_omni_realtime.py
ADDED
|
@@ -0,0 +1,425 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/python3
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
"""
|
| 4 |
+
https://help.aliyun.com/zh/model-studio/realtime#1234095db03g3
|
| 5 |
+
"""
|
| 6 |
+
import argparse
|
| 7 |
+
import asyncio
|
| 8 |
+
import base64
|
| 9 |
+
from enum import Enum
|
| 10 |
+
import json
|
| 11 |
+
import os
|
| 12 |
+
import queue
|
| 13 |
+
import threading
|
| 14 |
+
import time
|
| 15 |
+
from typing import Optional, Callable, List, Dict, Any
|
| 16 |
+
import websockets
|
| 17 |
+
|
| 18 |
+
import pyaudio
|
| 19 |
+
|
| 20 |
+
from project_settings import environment
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def get_args():
|
| 24 |
+
parser = argparse.ArgumentParser()
|
| 25 |
+
parser.add_argument(
|
| 26 |
+
"--qwen_api_key",
|
| 27 |
+
default=environment.get(key="QWEN_API_KEY"),
|
| 28 |
+
type=str
|
| 29 |
+
)
|
| 30 |
+
parser.add_argument(
|
| 31 |
+
"--model_name",
|
| 32 |
+
default="qwen-omni-turbo-realtime-2025-05-08",
|
| 33 |
+
type=str
|
| 34 |
+
)
|
| 35 |
+
args = parser.parse_args()
|
| 36 |
+
return args
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
class TurnDetectionMode(Enum):
|
| 40 |
+
SERVER_VAD = "server_vad"
|
| 41 |
+
MANUAL = "manual"
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
class OmniRealtimeClient:
|
| 45 |
+
"""
|
| 46 |
+
A demo client for interacting with the Omni Realtime API.
|
| 47 |
+
|
| 48 |
+
This class provides methods to connect to the Realtime API, send text and audio data,
|
| 49 |
+
handle responses, and manage the WebSocket connection.
|
| 50 |
+
|
| 51 |
+
Attributes:
|
| 52 |
+
base_url (str):
|
| 53 |
+
The base URL for the Realtime API.
|
| 54 |
+
api_key (str):
|
| 55 |
+
The API key for authentication.
|
| 56 |
+
model (str):
|
| 57 |
+
Omni model to use for chat.
|
| 58 |
+
voice (str):
|
| 59 |
+
The voice to use for audio output.
|
| 60 |
+
turn_detection_mode (TurnDetectionMode):
|
| 61 |
+
The mode for turn detection.
|
| 62 |
+
on_text_delta (Callable[[str], None]):
|
| 63 |
+
Callback for text delta events.
|
| 64 |
+
Takes in a string and returns nothing.
|
| 65 |
+
on_audio_delta (Callable[[bytes], None]):
|
| 66 |
+
Callback for audio delta events.
|
| 67 |
+
Takes in bytes and returns nothing.
|
| 68 |
+
on_input_transcript (Callable[[str], None]):
|
| 69 |
+
Callback for input transcript events.
|
| 70 |
+
Takes in a string and returns nothing.
|
| 71 |
+
on_interrupt (Callable[[], None]):
|
| 72 |
+
Callback for user interrupt events, should be used to stop audio playback.
|
| 73 |
+
on_output_transcript (Callable[[str], None]):
|
| 74 |
+
Callback for output transcript events.
|
| 75 |
+
Takes in a string and returns nothing.
|
| 76 |
+
extra_event_handlers (Dict[str, Callable[[Dict[str, Any]], None]]):
|
| 77 |
+
Additional event handlers.
|
| 78 |
+
Is a mapping of event names to functions that process the event payload.
|
| 79 |
+
"""
|
| 80 |
+
|
| 81 |
+
def __init__(
|
| 82 |
+
self,
|
| 83 |
+
base_url,
|
| 84 |
+
api_key: str,
|
| 85 |
+
model: str = "",
|
| 86 |
+
voice: str = "Chelsie",
|
| 87 |
+
turn_detection_mode: TurnDetectionMode = TurnDetectionMode.MANUAL,
|
| 88 |
+
on_text_delta: Optional[Callable[[str], None]] = None,
|
| 89 |
+
on_audio_delta: Optional[Callable[[bytes], None]] = None,
|
| 90 |
+
on_interrupt: Optional[Callable[[], None]] = None,
|
| 91 |
+
on_input_transcript: Optional[Callable[[str], None]] = None,
|
| 92 |
+
on_output_transcript: Optional[Callable[[str], None]] = None,
|
| 93 |
+
extra_event_handlers: Optional[Dict[str, Callable[[Dict[str, Any]], None]]] = None
|
| 94 |
+
):
|
| 95 |
+
self.base_url = base_url
|
| 96 |
+
self.api_key = api_key
|
| 97 |
+
self.model = model
|
| 98 |
+
self.voice = voice
|
| 99 |
+
self.ws = None
|
| 100 |
+
self.on_text_delta = on_text_delta
|
| 101 |
+
self.on_audio_delta = on_audio_delta
|
| 102 |
+
self.on_interrupt = on_interrupt
|
| 103 |
+
self.on_input_transcript = on_input_transcript
|
| 104 |
+
self.on_output_transcript = on_output_transcript
|
| 105 |
+
self.turn_detection_mode = turn_detection_mode
|
| 106 |
+
self.extra_event_handlers = extra_event_handlers or {}
|
| 107 |
+
|
| 108 |
+
# Track current response state
|
| 109 |
+
self._current_response_id = None
|
| 110 |
+
self._current_item_id = None
|
| 111 |
+
self._is_responding = False
|
| 112 |
+
# Track printing state for input and output transcripts
|
| 113 |
+
self._print_input_transcript = False
|
| 114 |
+
self._output_transcript_buffer = ""
|
| 115 |
+
|
| 116 |
+
async def connect(self) -> None:
|
| 117 |
+
"""Establish WebSocket connection with the Realtime API."""
|
| 118 |
+
url = f"{self.base_url}?model={self.model}"
|
| 119 |
+
headers = {
|
| 120 |
+
"Authorization": f"Bearer {self.api_key}"
|
| 121 |
+
}
|
| 122 |
+
print(f"url: {url}, headers: {headers}")
|
| 123 |
+
|
| 124 |
+
self.ws = await websockets.connect(url, additional_headers=headers)
|
| 125 |
+
|
| 126 |
+
# Set up default session configuration
|
| 127 |
+
if self.turn_detection_mode == TurnDetectionMode.MANUAL:
|
| 128 |
+
await self.update_session({
|
| 129 |
+
"modalities": ["text", "audio"],
|
| 130 |
+
"voice": self.voice,
|
| 131 |
+
"input_audio_format": "pcm16",
|
| 132 |
+
"output_audio_format": "pcm16",
|
| 133 |
+
"input_audio_transcription": {
|
| 134 |
+
"model": "gummy-realtime-v1"
|
| 135 |
+
},
|
| 136 |
+
"turn_detection": None
|
| 137 |
+
})
|
| 138 |
+
elif self.turn_detection_mode == TurnDetectionMode.SERVER_VAD:
|
| 139 |
+
await self.update_session({
|
| 140 |
+
"modalities": ["text", "audio"],
|
| 141 |
+
"voice": self.voice,
|
| 142 |
+
"input_audio_format": "pcm16",
|
| 143 |
+
"output_audio_format": "pcm16",
|
| 144 |
+
"input_audio_transcription": {
|
| 145 |
+
"model": "gummy-realtime-v1"
|
| 146 |
+
},
|
| 147 |
+
"turn_detection": {
|
| 148 |
+
"type": "server_vad",
|
| 149 |
+
"threshold": 0.1,
|
| 150 |
+
"prefix_padding_ms": 500,
|
| 151 |
+
"silence_duration_ms": 900
|
| 152 |
+
}
|
| 153 |
+
})
|
| 154 |
+
else:
|
| 155 |
+
raise ValueError(f"Invalid turn detection mode: {self.turn_detection_mode}")
|
| 156 |
+
|
| 157 |
+
async def send_event(self, event) -> None:
|
| 158 |
+
event['event_id'] = "event_" + str(int(time.time() * 1000))
|
| 159 |
+
print(f" Send event: type={event['type']}, event_id={event['event_id']}")
|
| 160 |
+
await self.ws.send(json.dumps(event))
|
| 161 |
+
|
| 162 |
+
async def update_session(self, config: Dict[str, Any]) -> None:
|
| 163 |
+
"""Update session configuration."""
|
| 164 |
+
event = {
|
| 165 |
+
"type": "session.update",
|
| 166 |
+
"session": config
|
| 167 |
+
}
|
| 168 |
+
print("update session: ", event)
|
| 169 |
+
await self.send_event(event)
|
| 170 |
+
|
| 171 |
+
async def stream_audio(self, audio_chunk: bytes) -> None:
|
| 172 |
+
"""Stream raw audio data to the API."""
|
| 173 |
+
# only support 16bit 16kHz mono pcm
|
| 174 |
+
audio_b64 = base64.b64encode(audio_chunk).decode()
|
| 175 |
+
|
| 176 |
+
append_event = {
|
| 177 |
+
"type": "input_audio_buffer.append",
|
| 178 |
+
"audio": audio_b64
|
| 179 |
+
}
|
| 180 |
+
await self.send_event(json.dumps(append_event))
|
| 181 |
+
|
| 182 |
+
async def create_response(self) -> None:
|
| 183 |
+
"""Request a response from the API. Needed when using manual mode."""
|
| 184 |
+
event = {
|
| 185 |
+
"type": "response.create",
|
| 186 |
+
"response": {
|
| 187 |
+
"instructions": "你是Tom,一个美国的导购,负责售卖手机、电视",
|
| 188 |
+
"modalities": ["text", "audio"]
|
| 189 |
+
}
|
| 190 |
+
}
|
| 191 |
+
print("create response: ", event)
|
| 192 |
+
await self.send_event(event)
|
| 193 |
+
|
| 194 |
+
async def cancel_response(self) -> None:
|
| 195 |
+
"""Cancel the current response."""
|
| 196 |
+
event = {
|
| 197 |
+
"type": "response.cancel"
|
| 198 |
+
}
|
| 199 |
+
await self.send_event(event)
|
| 200 |
+
|
| 201 |
+
async def handle_interruption(self):
|
| 202 |
+
"""Handle user interruption of the current response."""
|
| 203 |
+
if not self._is_responding:
|
| 204 |
+
return
|
| 205 |
+
|
| 206 |
+
print(" Handling interruption")
|
| 207 |
+
|
| 208 |
+
# 1. Cancel the current response
|
| 209 |
+
if self._current_response_id:
|
| 210 |
+
await self.cancel_response()
|
| 211 |
+
|
| 212 |
+
self._is_responding = False
|
| 213 |
+
self._current_response_id = None
|
| 214 |
+
self._current_item_id = None
|
| 215 |
+
|
| 216 |
+
async def handle_messages(self) -> None:
|
| 217 |
+
try:
|
| 218 |
+
async for message in self.ws:
|
| 219 |
+
event = json.loads(message)
|
| 220 |
+
event_type = event.get("type")
|
| 221 |
+
|
| 222 |
+
if event_type != "response.audio.delta":
|
| 223 |
+
print(" event: ", event)
|
| 224 |
+
else:
|
| 225 |
+
print(" event_type: ", event_type)
|
| 226 |
+
|
| 227 |
+
if event_type == "error":
|
| 228 |
+
print(" Error: ", event['error'])
|
| 229 |
+
continue
|
| 230 |
+
elif event_type == "response.created":
|
| 231 |
+
self._current_response_id = event.get("response", {}).get("id")
|
| 232 |
+
self._is_responding = True
|
| 233 |
+
elif event_type == "response.output_item.added":
|
| 234 |
+
self._current_item_id = event.get("item", {}).get("id")
|
| 235 |
+
elif event_type == "response.done":
|
| 236 |
+
self._is_responding = False
|
| 237 |
+
self._current_response_id = None
|
| 238 |
+
self._current_item_id = None
|
| 239 |
+
# Handle interruptions
|
| 240 |
+
elif event_type == "input_audio_buffer.speech_started":
|
| 241 |
+
print(" Speech detected")
|
| 242 |
+
if self._is_responding:
|
| 243 |
+
print(" Handling interruption")
|
| 244 |
+
await self.handle_interruption()
|
| 245 |
+
|
| 246 |
+
if self.on_interrupt:
|
| 247 |
+
print(" Handling on_interrupt, stop playback")
|
| 248 |
+
self.on_interrupt()
|
| 249 |
+
elif event_type == "input_audio_buffer.speech_stopped":
|
| 250 |
+
print(" Speech ended")
|
| 251 |
+
# Handle normal response events
|
| 252 |
+
elif event_type == "response.text.delta":
|
| 253 |
+
if self.on_text_delta:
|
| 254 |
+
self.on_text_delta(event["delta"])
|
| 255 |
+
elif event_type == "response.audio.delta":
|
| 256 |
+
if self.on_audio_delta:
|
| 257 |
+
audio_bytes = base64.b64decode(event["delta"])
|
| 258 |
+
self.on_audio_delta(audio_bytes)
|
| 259 |
+
elif event_type == "conversation.item.input_audio_transcription.completed":
|
| 260 |
+
transcript = event.get("transcript", "")
|
| 261 |
+
if self.on_input_transcript:
|
| 262 |
+
await asyncio.to_thread(self.on_input_transcript, transcript)
|
| 263 |
+
self._print_input_transcript = True
|
| 264 |
+
elif event_type == "response.audio_transcript.delta":
|
| 265 |
+
if self.on_output_transcript:
|
| 266 |
+
delta = event.get("delta", "")
|
| 267 |
+
if not self._print_input_transcript:
|
| 268 |
+
self._output_transcript_buffer += delta
|
| 269 |
+
else:
|
| 270 |
+
if self._output_transcript_buffer:
|
| 271 |
+
await asyncio.to_thread(self.on_output_transcript, self._output_transcript_buffer)
|
| 272 |
+
self._output_transcript_buffer = ""
|
| 273 |
+
await asyncio.to_thread(self.on_output_transcript, delta)
|
| 274 |
+
elif event_type == "response.audio_transcript.done":
|
| 275 |
+
self._print_input_transcript = False
|
| 276 |
+
elif event_type in self.extra_event_handlers:
|
| 277 |
+
self.extra_event_handlers[event_type](event)
|
| 278 |
+
|
| 279 |
+
except websockets.exceptions.ConnectionClosed:
|
| 280 |
+
print(" Connection closed")
|
| 281 |
+
except Exception as e:
|
| 282 |
+
print(" Error in message handling: ", str(e))
|
| 283 |
+
|
| 284 |
+
async def close(self) -> None:
|
| 285 |
+
"""Close the WebSocket connection."""
|
| 286 |
+
if self.ws:
|
| 287 |
+
await self.ws.close()
|
| 288 |
+
|
| 289 |
+
|
| 290 |
+
class OmniRealtime(object):
|
| 291 |
+
def __init__(self,
|
| 292 |
+
qwen_api_key: str,
|
| 293 |
+
model_name: str,
|
| 294 |
+
sample_rate: int = 16000,
|
| 295 |
+
chunk_size: int = 3200,
|
| 296 |
+
channels: int = 1,
|
| 297 |
+
):
|
| 298 |
+
self.qwen_api_key = qwen_api_key
|
| 299 |
+
self.model_name = model_name
|
| 300 |
+
self.sample_rate = sample_rate
|
| 301 |
+
self.chunk_size = chunk_size
|
| 302 |
+
self.channels = channels
|
| 303 |
+
|
| 304 |
+
# 创建一个全局音频队列和播放线程
|
| 305 |
+
self.audio_queue = queue.Queue()
|
| 306 |
+
self.audio_player = None
|
| 307 |
+
|
| 308 |
+
self.realtime_client = OmniRealtimeClient(
|
| 309 |
+
base_url="wss://dashscope.aliyuncs.com/api-ws/v1/realtime",
|
| 310 |
+
api_key=self.qwen_api_key,
|
| 311 |
+
model=self.model_name,
|
| 312 |
+
voice="Chelsie",
|
| 313 |
+
on_text_delta=lambda text: print(f"\nAssistant: {text}", end="", flush=True),
|
| 314 |
+
on_audio_delta=self.handle_audio_data,
|
| 315 |
+
# turn_detection_mode=TurnDetectionMode.MANUAL,
|
| 316 |
+
turn_detection_mode=TurnDetectionMode.SERVER_VAD,
|
| 317 |
+
)
|
| 318 |
+
|
| 319 |
+
def start_audio_player(self):
|
| 320 |
+
"""启动音频播放线程"""
|
| 321 |
+
if self.audio_player is None or not self.audio_player.is_alive():
|
| 322 |
+
audio_player = threading.Thread(target=self.audio_player_thread, daemon=True)
|
| 323 |
+
audio_player.start()
|
| 324 |
+
|
| 325 |
+
def audio_player_thread(self):
|
| 326 |
+
"""后台线程用于播放音频数据"""
|
| 327 |
+
p = pyaudio.PyAudio()
|
| 328 |
+
stream = p.open(format=pyaudio.paInt16,
|
| 329 |
+
channels=self.channels,
|
| 330 |
+
rate=24000,
|
| 331 |
+
output=True,
|
| 332 |
+
frames_per_buffer=self.chunk_size)
|
| 333 |
+
|
| 334 |
+
try:
|
| 335 |
+
while True:
|
| 336 |
+
try:
|
| 337 |
+
# 从队列获取音频数据
|
| 338 |
+
audio_data = self.audio_queue.get(block=True, timeout=0.5)
|
| 339 |
+
if audio_data is None: # 结束信号
|
| 340 |
+
break
|
| 341 |
+
# 播放音频数据
|
| 342 |
+
stream.write(audio_data)
|
| 343 |
+
self.audio_queue.task_done()
|
| 344 |
+
except queue.Empty:
|
| 345 |
+
# 如果队列为空,继续等待
|
| 346 |
+
continue
|
| 347 |
+
finally:
|
| 348 |
+
# 清理
|
| 349 |
+
stream.stop_stream()
|
| 350 |
+
stream.close()
|
| 351 |
+
|
| 352 |
+
def handle_audio_data(self, audio_data: bytes):
|
| 353 |
+
"""处理接收到的音频数据"""
|
| 354 |
+
# 打印接收到的音频数据长度(调试用)
|
| 355 |
+
print(f"\n接收到音频数据: {len(audio_data)} 字节")
|
| 356 |
+
# 将音频数据放入队列
|
| 357 |
+
self.audio_queue.put(audio_data)
|
| 358 |
+
|
| 359 |
+
async def start_microphone_streaming(self):
|
| 360 |
+
p = pyaudio.PyAudio()
|
| 361 |
+
stream = p.open(format=pyaudio.paInt16,
|
| 362 |
+
channels=self.channels,
|
| 363 |
+
rate=self.sample_rate,
|
| 364 |
+
input=True,
|
| 365 |
+
frames_per_buffer=self.chunk_size)
|
| 366 |
+
|
| 367 |
+
try:
|
| 368 |
+
print("开始录音,请讲话...")
|
| 369 |
+
while True:
|
| 370 |
+
audio_data = stream.read(self.chunk_size)
|
| 371 |
+
encoded_data = base64.b64encode(audio_data).decode("utf-8")
|
| 372 |
+
|
| 373 |
+
eventd = {
|
| 374 |
+
"event_id": "event_" + str(int(time.time() * 1000)),
|
| 375 |
+
"type": "input_audio_buffer.append",
|
| 376 |
+
"audio": encoded_data
|
| 377 |
+
}
|
| 378 |
+
await self.realtime_client.send_event(eventd)
|
| 379 |
+
|
| 380 |
+
# 保持较短的等待时间以模拟实时交互
|
| 381 |
+
await asyncio.sleep(0.05)
|
| 382 |
+
finally:
|
| 383 |
+
stream.stop_stream()
|
| 384 |
+
stream.close()
|
| 385 |
+
p.terminate()
|
| 386 |
+
|
| 387 |
+
async def run(self):
|
| 388 |
+
self.start_audio_player()
|
| 389 |
+
|
| 390 |
+
await self.realtime_client.connect()
|
| 391 |
+
# await self.realtime_client.create_response()
|
| 392 |
+
|
| 393 |
+
try:
|
| 394 |
+
# 启动消息处理和麦克风录音
|
| 395 |
+
message_handler = asyncio.create_task(self.realtime_client.handle_messages())
|
| 396 |
+
streaming_task = asyncio.create_task(self.start_microphone_streaming())
|
| 397 |
+
|
| 398 |
+
while True:
|
| 399 |
+
await asyncio.Queue().get()
|
| 400 |
+
except Exception as e:
|
| 401 |
+
print(f"Error: {e}")
|
| 402 |
+
finally:
|
| 403 |
+
# 结束音频播放线程
|
| 404 |
+
self.audio_queue.put(None)
|
| 405 |
+
if self.audio_player:
|
| 406 |
+
self.audio_player.join(timeout=1)
|
| 407 |
+
await self.realtime_client.close()
|
| 408 |
+
|
| 409 |
+
return
|
| 410 |
+
|
| 411 |
+
|
| 412 |
+
async def main():
|
| 413 |
+
args = get_args()
|
| 414 |
+
|
| 415 |
+
omni_realtime = OmniRealtime(
|
| 416 |
+
qwen_api_key=args.qwen_api_key,
|
| 417 |
+
model_name=args.model_name,
|
| 418 |
+
)
|
| 419 |
+
|
| 420 |
+
await omni_realtime.run()
|
| 421 |
+
return
|
| 422 |
+
|
| 423 |
+
|
| 424 |
+
if __name__ == "__main__":
|
| 425 |
+
asyncio.run(main())
|
examples/tutorial/text_generation.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/python3
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
"""
|
| 4 |
+
https://ai.google.dev/gemini-api/docs/text-generation?hl=zh-cn
|
| 5 |
+
"""
|
| 6 |
+
import argparse
|
| 7 |
+
|
| 8 |
+
from google import genai
|
| 9 |
+
from google.genai import types
|
| 10 |
+
|
| 11 |
+
from project_settings import environment
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def get_args():
|
| 15 |
+
parser = argparse.ArgumentParser()
|
| 16 |
+
parser.add_argument(
|
| 17 |
+
"--gemini_api_key",
|
| 18 |
+
default=environment.get(key="GEMINI_API_KEY"),
|
| 19 |
+
type=str
|
| 20 |
+
)
|
| 21 |
+
parser.add_argument(
|
| 22 |
+
"--model",
|
| 23 |
+
default="gemini-2.0-flash",
|
| 24 |
+
# default="gemini-2.5-pro-preview-05-06",
|
| 25 |
+
type=str
|
| 26 |
+
)
|
| 27 |
+
args = parser.parse_args()
|
| 28 |
+
return args
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def main():
|
| 32 |
+
args = get_args()
|
| 33 |
+
|
| 34 |
+
client = genai.Client(api_key=args.gemini_api_key)
|
| 35 |
+
|
| 36 |
+
response = client.models.generate_content(
|
| 37 |
+
model=args.model,
|
| 38 |
+
config=types.GenerateContentConfig(
|
| 39 |
+
system_instruction="You are a cat. Your name is Neko."),
|
| 40 |
+
contents="Hello there"
|
| 41 |
+
)
|
| 42 |
+
|
| 43 |
+
print(response.text)
|
| 44 |
+
|
| 45 |
+
return
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
if __name__ == "__main__":
|
| 49 |
+
main()
|
examples/vertexai_start/gemini-2.0-test.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
|
| 3 |
+
from google import genai
|
| 4 |
+
from google.genai import types
|
| 5 |
+
import base64
|
| 6 |
+
|
| 7 |
+
def generate():
|
| 8 |
+
client = genai.Client(
|
| 9 |
+
vertexai=True,
|
| 10 |
+
project="potent-veld-462405-t3",
|
| 11 |
+
location="global",
|
| 12 |
+
)
|
| 13 |
+
|
| 14 |
+
model = "gemini-2.0-flash-001"
|
| 15 |
+
contents = [
|
| 16 |
+
types.Content(
|
| 17 |
+
role="user",
|
| 18 |
+
parts=[
|
| 19 |
+
types.Part.from_text(text="""今天深圳的天气怎么样?""")
|
| 20 |
+
]
|
| 21 |
+
)
|
| 22 |
+
]
|
| 23 |
+
|
| 24 |
+
tools = [
|
| 25 |
+
types.Tool(google_search=types.GoogleSearch()),
|
| 26 |
+
]
|
| 27 |
+
|
| 28 |
+
generate_content_config = types.GenerateContentConfig(
|
| 29 |
+
temperature = 1,
|
| 30 |
+
top_p = 0.95,
|
| 31 |
+
max_output_tokens = 8192,
|
| 32 |
+
response_modalities = ["TEXT"],
|
| 33 |
+
tools=tools,
|
| 34 |
+
)
|
| 35 |
+
|
| 36 |
+
for chunk in client.models.generate_content_stream(
|
| 37 |
+
model = model,
|
| 38 |
+
contents = contents,
|
| 39 |
+
config = generate_content_config,
|
| 40 |
+
):
|
| 41 |
+
print(chunk.text, end="")
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
# os.environ["http_proxy"] = "http://127.0.0.1:7890"
|
| 45 |
+
# os.environ["https_proxy"] = "http://127.0.0.1:7890"
|
| 46 |
+
# os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = "/Users/huangjiajun/Project/baidao-test-666808-4b2251497ed0.json"
|
| 47 |
+
os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = r"E:\Users\tianx\intelli-zen\OpenGeminiAPI\dotenv\potent-veld-462405-t3-8091a29b2894.json"
|
| 48 |
+
generate()
|
examples/vertexai_start/gemini-2.5-test.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/python3
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
import os
|
| 4 |
+
|
| 5 |
+
from google import genai
|
| 6 |
+
from google.genai import types
|
| 7 |
+
|
| 8 |
+
os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = r"E:\Users\tianx\intelli-zen\OpenGeminiAPI\dotenv\potent-veld-462405-t3-8091a29b2894.json"
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
client = genai.Client(
|
| 12 |
+
vertexai=True,
|
| 13 |
+
project="potent-veld-462405-t3",
|
| 14 |
+
location="global",
|
| 15 |
+
)
|
| 16 |
+
|
| 17 |
+
# model = "gemini-2.0-flash-001"
|
| 18 |
+
model = "gemini-2.5-flash"
|
| 19 |
+
contents = [
|
| 20 |
+
types.Content(
|
| 21 |
+
role="user",
|
| 22 |
+
parts=[
|
| 23 |
+
types.Part.from_text(text="""今天深圳的天气怎么样?""")
|
| 24 |
+
]
|
| 25 |
+
)
|
| 26 |
+
]
|
| 27 |
+
|
| 28 |
+
generate_content_config = types.GenerateContentConfig(
|
| 29 |
+
temperature=1,
|
| 30 |
+
top_p=0.95,
|
| 31 |
+
max_output_tokens=8192,
|
| 32 |
+
response_modalities=["TEXT"],
|
| 33 |
+
)
|
| 34 |
+
response: types.GenerateContentResponse = client.models.generate_content(
|
| 35 |
+
model=model,
|
| 36 |
+
contents=contents,
|
| 37 |
+
config=generate_content_config,
|
| 38 |
+
)
|
| 39 |
+
text = response.candidates[0].content.parts[0].text
|
| 40 |
+
print(text)
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
if __name__ == "__main__":
|
| 44 |
+
pass
|
install.sh
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
|
| 3 |
+
# bash install.sh --stage 1 --stop_stage 1 --system_version windows
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
system_version="centos";
|
| 7 |
+
verbose=true;
|
| 8 |
+
stage=-1
|
| 9 |
+
stop_stage=0
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
# parse options
|
| 13 |
+
while true; do
|
| 14 |
+
[ -z "${1:-}" ] && break; # break if there are no arguments
|
| 15 |
+
case "$1" in
|
| 16 |
+
--*) name=$(echo "$1" | sed s/^--// | sed s/-/_/g);
|
| 17 |
+
eval '[ -z "${'"$name"'+xxx}" ]' && echo "$0: invalid option $1" 1>&2 && exit 1;
|
| 18 |
+
old_value="(eval echo \\$$name)";
|
| 19 |
+
if [ "${old_value}" == "true" ] || [ "${old_value}" == "false" ]; then
|
| 20 |
+
was_bool=true;
|
| 21 |
+
else
|
| 22 |
+
was_bool=false;
|
| 23 |
+
fi
|
| 24 |
+
|
| 25 |
+
# Set the variable to the right value-- the escaped quotes make it work if
|
| 26 |
+
# the option had spaces, like --cmd "queue.pl -sync y"
|
| 27 |
+
eval "${name}=\"$2\"";
|
| 28 |
+
|
| 29 |
+
# Check that Boolean-valued arguments are really Boolean.
|
| 30 |
+
if $was_bool && [[ "$2" != "true" && "$2" != "false" ]]; then
|
| 31 |
+
echo "$0: expected \"true\" or \"false\": $1 $2" 1>&2
|
| 32 |
+
exit 1;
|
| 33 |
+
fi
|
| 34 |
+
shift 2;
|
| 35 |
+
;;
|
| 36 |
+
|
| 37 |
+
*) break;
|
| 38 |
+
esac
|
| 39 |
+
done
|
| 40 |
+
|
| 41 |
+
work_dir="$(pwd)"
|
| 42 |
+
data_dir="${work_dir}/data/impulse_responses"
|
| 43 |
+
|
| 44 |
+
mkdir -p "${data_dir}"
|
| 45 |
+
|
| 46 |
+
if [ ${stage} -le 1 ] && [ ${stop_stage} -ge 1 ]; then
|
| 47 |
+
$verbose && echo "stage 1: download simulated room impulse responses"
|
| 48 |
+
cd "${data_dir}" || exit 1;
|
| 49 |
+
|
| 50 |
+
# https://www.openslr.org/26/
|
| 51 |
+
# wget https://www.openslr.org/resources/26/sim_rir_8k.zip
|
| 52 |
+
# unzip sim_rir_8k.zip
|
| 53 |
+
|
| 54 |
+
wget https://www.openslr.org/resources/26/sim_rir_16k.zip
|
| 55 |
+
unzip sim_rir_16k.zip
|
| 56 |
+
|
| 57 |
+
# https://www.openslr.org/28/
|
| 58 |
+
# wget https://www.openslr.org/resources/28/rirs_noises.zip
|
| 59 |
+
# unzip rirs_noises.zip
|
| 60 |
+
|
| 61 |
+
fi
|
| 62 |
+
|
log.py
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/python3
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
import logging
|
| 4 |
+
from logging.handlers import RotatingFileHandler, TimedRotatingFileHandler
|
| 5 |
+
import os
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def setup_size_rotating(log_directory: str):
|
| 9 |
+
fmt = "%(asctime)s - %(name)s - %(levelname)s %(filename)s:%(lineno)d > %(message)s"
|
| 10 |
+
|
| 11 |
+
stream_handler = logging.StreamHandler()
|
| 12 |
+
stream_handler.setLevel(logging.INFO)
|
| 13 |
+
stream_handler.setFormatter(logging.Formatter(fmt))
|
| 14 |
+
|
| 15 |
+
# main
|
| 16 |
+
main_logger = logging.getLogger("main")
|
| 17 |
+
main_logger.addHandler(stream_handler)
|
| 18 |
+
main_info_file_handler = RotatingFileHandler(
|
| 19 |
+
filename=os.path.join(log_directory, "main.log"),
|
| 20 |
+
maxBytes=100*1024*1024, # 100MB
|
| 21 |
+
encoding="utf-8",
|
| 22 |
+
backupCount=2,
|
| 23 |
+
)
|
| 24 |
+
main_info_file_handler.setLevel(logging.INFO)
|
| 25 |
+
main_info_file_handler.setFormatter(logging.Formatter(fmt))
|
| 26 |
+
main_logger.addHandler(main_info_file_handler)
|
| 27 |
+
|
| 28 |
+
# http
|
| 29 |
+
http_logger = logging.getLogger("http")
|
| 30 |
+
http_file_handler = RotatingFileHandler(
|
| 31 |
+
filename=os.path.join(log_directory, "http.log"),
|
| 32 |
+
maxBytes=100*1024*1024, # 100MB
|
| 33 |
+
encoding="utf-8",
|
| 34 |
+
backupCount=2,
|
| 35 |
+
)
|
| 36 |
+
http_file_handler.setLevel(logging.DEBUG)
|
| 37 |
+
http_file_handler.setFormatter(logging.Formatter(fmt))
|
| 38 |
+
http_logger.addHandler(http_file_handler)
|
| 39 |
+
|
| 40 |
+
# api
|
| 41 |
+
api_logger = logging.getLogger("api")
|
| 42 |
+
api_file_handler = RotatingFileHandler(
|
| 43 |
+
filename=os.path.join(log_directory, "api.log"),
|
| 44 |
+
maxBytes=10*1024*1024, # 10MB
|
| 45 |
+
encoding="utf-8",
|
| 46 |
+
backupCount=2,
|
| 47 |
+
)
|
| 48 |
+
api_file_handler.setLevel(logging.DEBUG)
|
| 49 |
+
api_file_handler.setFormatter(logging.Formatter(fmt))
|
| 50 |
+
api_logger.addHandler(api_file_handler)
|
| 51 |
+
|
| 52 |
+
# toolbox
|
| 53 |
+
toolbox_logger = logging.getLogger("toolbox")
|
| 54 |
+
toolbox_logger.addHandler(stream_handler)
|
| 55 |
+
toolbox_file_handler = RotatingFileHandler(
|
| 56 |
+
filename=os.path.join(log_directory, "toolbox.log"),
|
| 57 |
+
maxBytes=10*1024*1024, # 10MB
|
| 58 |
+
encoding="utf-8",
|
| 59 |
+
backupCount=2,
|
| 60 |
+
)
|
| 61 |
+
toolbox_file_handler.setLevel(logging.DEBUG)
|
| 62 |
+
toolbox_file_handler.setFormatter(logging.Formatter(fmt))
|
| 63 |
+
toolbox_logger.addHandler(toolbox_file_handler)
|
| 64 |
+
|
| 65 |
+
# alarm
|
| 66 |
+
alarm_logger = logging.getLogger("alarm")
|
| 67 |
+
alarm_file_handler = RotatingFileHandler(
|
| 68 |
+
filename=os.path.join(log_directory, "alarm.log"),
|
| 69 |
+
maxBytes=1*1024*1024, # 1MB
|
| 70 |
+
encoding="utf-8",
|
| 71 |
+
backupCount=2,
|
| 72 |
+
)
|
| 73 |
+
alarm_file_handler.setLevel(logging.DEBUG)
|
| 74 |
+
alarm_file_handler.setFormatter(logging.Formatter(fmt))
|
| 75 |
+
alarm_logger.addHandler(alarm_file_handler)
|
| 76 |
+
|
| 77 |
+
debug_file_handler = RotatingFileHandler(
|
| 78 |
+
filename=os.path.join(log_directory, "debug.log"),
|
| 79 |
+
maxBytes=1*1024*1024, # 1MB
|
| 80 |
+
encoding="utf-8",
|
| 81 |
+
backupCount=2,
|
| 82 |
+
)
|
| 83 |
+
debug_file_handler.setLevel(logging.DEBUG)
|
| 84 |
+
debug_file_handler.setFormatter(logging.Formatter(fmt))
|
| 85 |
+
|
| 86 |
+
info_file_handler = RotatingFileHandler(
|
| 87 |
+
filename=os.path.join(log_directory, "info.log"),
|
| 88 |
+
maxBytes=1*1024*1024, # 1MB
|
| 89 |
+
encoding="utf-8",
|
| 90 |
+
backupCount=2,
|
| 91 |
+
)
|
| 92 |
+
info_file_handler.setLevel(logging.INFO)
|
| 93 |
+
info_file_handler.setFormatter(logging.Formatter(fmt))
|
| 94 |
+
|
| 95 |
+
error_file_handler = RotatingFileHandler(
|
| 96 |
+
filename=os.path.join(log_directory, "error.log"),
|
| 97 |
+
maxBytes=1*1024*1024, # 1MB
|
| 98 |
+
encoding="utf-8",
|
| 99 |
+
backupCount=2,
|
| 100 |
+
)
|
| 101 |
+
error_file_handler.setLevel(logging.ERROR)
|
| 102 |
+
error_file_handler.setFormatter(logging.Formatter(fmt))
|
| 103 |
+
|
| 104 |
+
logging.basicConfig(
|
| 105 |
+
level=logging.DEBUG,
|
| 106 |
+
datefmt="%a, %d %b %Y %H:%M:%S",
|
| 107 |
+
handlers=[
|
| 108 |
+
debug_file_handler,
|
| 109 |
+
info_file_handler,
|
| 110 |
+
error_file_handler,
|
| 111 |
+
]
|
| 112 |
+
)
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
def setup_time_rotating(log_directory: str):
|
| 116 |
+
fmt = "%(asctime)s - %(name)s - %(levelname)s %(filename)s:%(lineno)d > %(message)s"
|
| 117 |
+
|
| 118 |
+
stream_handler = logging.StreamHandler()
|
| 119 |
+
stream_handler.setLevel(logging.INFO)
|
| 120 |
+
stream_handler.setFormatter(logging.Formatter(fmt))
|
| 121 |
+
|
| 122 |
+
# main
|
| 123 |
+
main_logger = logging.getLogger("main")
|
| 124 |
+
main_logger.addHandler(stream_handler)
|
| 125 |
+
main_info_file_handler = TimedRotatingFileHandler(
|
| 126 |
+
filename=os.path.join(log_directory, "main.log"),
|
| 127 |
+
encoding="utf-8",
|
| 128 |
+
when="midnight",
|
| 129 |
+
interval=1,
|
| 130 |
+
backupCount=7
|
| 131 |
+
)
|
| 132 |
+
main_info_file_handler.setLevel(logging.INFO)
|
| 133 |
+
main_info_file_handler.setFormatter(logging.Formatter(fmt))
|
| 134 |
+
main_logger.addHandler(main_info_file_handler)
|
| 135 |
+
|
| 136 |
+
# http
|
| 137 |
+
http_logger = logging.getLogger("http")
|
| 138 |
+
http_file_handler = TimedRotatingFileHandler(
|
| 139 |
+
filename=os.path.join(log_directory, "http.log"),
|
| 140 |
+
encoding='utf-8',
|
| 141 |
+
when="midnight",
|
| 142 |
+
interval=1,
|
| 143 |
+
backupCount=7
|
| 144 |
+
)
|
| 145 |
+
http_file_handler.setLevel(logging.DEBUG)
|
| 146 |
+
http_file_handler.setFormatter(logging.Formatter(fmt))
|
| 147 |
+
http_logger.addHandler(http_file_handler)
|
| 148 |
+
|
| 149 |
+
# api
|
| 150 |
+
api_logger = logging.getLogger("api")
|
| 151 |
+
api_file_handler = TimedRotatingFileHandler(
|
| 152 |
+
filename=os.path.join(log_directory, "api.log"),
|
| 153 |
+
encoding='utf-8',
|
| 154 |
+
when="midnight",
|
| 155 |
+
interval=1,
|
| 156 |
+
backupCount=7
|
| 157 |
+
)
|
| 158 |
+
api_file_handler.setLevel(logging.DEBUG)
|
| 159 |
+
api_file_handler.setFormatter(logging.Formatter(fmt))
|
| 160 |
+
api_logger.addHandler(api_file_handler)
|
| 161 |
+
|
| 162 |
+
# toolbox
|
| 163 |
+
toolbox_logger = logging.getLogger("toolbox")
|
| 164 |
+
toolbox_file_handler = RotatingFileHandler(
|
| 165 |
+
filename=os.path.join(log_directory, "toolbox.log"),
|
| 166 |
+
maxBytes=10*1024*1024, # 10MB
|
| 167 |
+
encoding="utf-8",
|
| 168 |
+
backupCount=2,
|
| 169 |
+
)
|
| 170 |
+
toolbox_file_handler.setLevel(logging.DEBUG)
|
| 171 |
+
toolbox_file_handler.setFormatter(logging.Formatter(fmt))
|
| 172 |
+
toolbox_logger.addHandler(toolbox_file_handler)
|
| 173 |
+
|
| 174 |
+
# alarm
|
| 175 |
+
alarm_logger = logging.getLogger("alarm")
|
| 176 |
+
alarm_file_handler = TimedRotatingFileHandler(
|
| 177 |
+
filename=os.path.join(log_directory, "alarm.log"),
|
| 178 |
+
encoding="utf-8",
|
| 179 |
+
when="midnight",
|
| 180 |
+
interval=1,
|
| 181 |
+
backupCount=7
|
| 182 |
+
)
|
| 183 |
+
alarm_file_handler.setLevel(logging.DEBUG)
|
| 184 |
+
alarm_file_handler.setFormatter(logging.Formatter(fmt))
|
| 185 |
+
alarm_logger.addHandler(alarm_file_handler)
|
| 186 |
+
|
| 187 |
+
debug_file_handler = TimedRotatingFileHandler(
|
| 188 |
+
filename=os.path.join(log_directory, "debug.log"),
|
| 189 |
+
encoding="utf-8",
|
| 190 |
+
when="D",
|
| 191 |
+
interval=1,
|
| 192 |
+
backupCount=7
|
| 193 |
+
)
|
| 194 |
+
debug_file_handler.setLevel(logging.DEBUG)
|
| 195 |
+
debug_file_handler.setFormatter(logging.Formatter(fmt))
|
| 196 |
+
|
| 197 |
+
info_file_handler = TimedRotatingFileHandler(
|
| 198 |
+
filename=os.path.join(log_directory, "info.log"),
|
| 199 |
+
encoding="utf-8",
|
| 200 |
+
when="D",
|
| 201 |
+
interval=1,
|
| 202 |
+
backupCount=7
|
| 203 |
+
)
|
| 204 |
+
info_file_handler.setLevel(logging.INFO)
|
| 205 |
+
info_file_handler.setFormatter(logging.Formatter(fmt))
|
| 206 |
+
|
| 207 |
+
error_file_handler = TimedRotatingFileHandler(
|
| 208 |
+
filename=os.path.join(log_directory, "error.log"),
|
| 209 |
+
encoding="utf-8",
|
| 210 |
+
when="D",
|
| 211 |
+
interval=1,
|
| 212 |
+
backupCount=7
|
| 213 |
+
)
|
| 214 |
+
error_file_handler.setLevel(logging.ERROR)
|
| 215 |
+
error_file_handler.setFormatter(logging.Formatter(fmt))
|
| 216 |
+
|
| 217 |
+
logging.basicConfig(
|
| 218 |
+
level=logging.DEBUG,
|
| 219 |
+
datefmt="%a, %d %b %Y %H:%M:%S",
|
| 220 |
+
handlers=[
|
| 221 |
+
debug_file_handler,
|
| 222 |
+
info_file_handler,
|
| 223 |
+
error_file_handler,
|
| 224 |
+
]
|
| 225 |
+
)
|
| 226 |
+
|
| 227 |
+
|
| 228 |
+
if __name__ == "__main__":
|
| 229 |
+
pass
|
main.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/python3
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
import argparse
|
| 4 |
+
import asyncio
|
| 5 |
+
import json
|
| 6 |
+
import logging
|
| 7 |
+
from pathlib import Path
|
| 8 |
+
import platform
|
| 9 |
+
import tempfile
|
| 10 |
+
from typing import Tuple, List
|
| 11 |
+
import uuid
|
| 12 |
+
|
| 13 |
+
import gradio as gr
|
| 14 |
+
import numpy as np
|
| 15 |
+
from scipy.io import wavfile
|
| 16 |
+
|
| 17 |
+
from project_settings import environment, project_path, log_directory
|
| 18 |
+
from toolbox.os.command import Command
|
| 19 |
+
import log
|
| 20 |
+
|
| 21 |
+
log.setup_size_rotating(log_directory=log_directory)
|
| 22 |
+
|
| 23 |
+
logger = logging.getLogger("main")
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def get_args():
|
| 27 |
+
parser = argparse.ArgumentParser()
|
| 28 |
+
parser.add_argument(
|
| 29 |
+
"--examples_dir",
|
| 30 |
+
default=(project_path / "data/examples").as_posix(),
|
| 31 |
+
type=str,
|
| 32 |
+
)
|
| 33 |
+
args = parser.parse_args()
|
| 34 |
+
return args
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def shell(cmd: str):
|
| 38 |
+
return Command.popen(cmd)
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def main():
|
| 42 |
+
args = get_args()
|
| 43 |
+
|
| 44 |
+
# ui
|
| 45 |
+
with gr.Blocks() as blocks:
|
| 46 |
+
with gr.Tabs():
|
| 47 |
+
with gr.TabItem("shell"):
|
| 48 |
+
shell_text = gr.Textbox(label="cmd")
|
| 49 |
+
shell_button = gr.Button("run")
|
| 50 |
+
shell_output = gr.Textbox(label="output")
|
| 51 |
+
|
| 52 |
+
shell_button.click(
|
| 53 |
+
shell,
|
| 54 |
+
inputs=[shell_text,],
|
| 55 |
+
outputs=[shell_output],
|
| 56 |
+
)
|
| 57 |
+
|
| 58 |
+
# http://127.0.0.1:7861/
|
| 59 |
+
# http://10.75.27.247:7861/
|
| 60 |
+
blocks.queue().launch(
|
| 61 |
+
share=False if platform.system() == "Windows" else False,
|
| 62 |
+
server_name="127.0.0.1" if platform.system() == "Windows" else "0.0.0.0",
|
| 63 |
+
# server_name="0.0.0.0",
|
| 64 |
+
server_port=environment.get("port", 7860, dtype=int),
|
| 65 |
+
)
|
| 66 |
+
return
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
if __name__ == "__main__":
|
| 70 |
+
main()
|
project_settings.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/python3
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
import os
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
|
| 6 |
+
from toolbox.os.environment import EnvironmentManager
|
| 7 |
+
|
| 8 |
+
project_path = os.path.abspath(os.path.dirname(__file__))
|
| 9 |
+
project_path = Path(project_path)
|
| 10 |
+
|
| 11 |
+
log_directory = project_path / "logs"
|
| 12 |
+
log_directory.mkdir(parents=True, exist_ok=True)
|
| 13 |
+
|
| 14 |
+
environment = EnvironmentManager(
|
| 15 |
+
path=os.path.join(project_path, "dotenv"),
|
| 16 |
+
env=os.environ.get("environment", "dev"),
|
| 17 |
+
)
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
if __name__ == '__main__':
|
| 21 |
+
pass
|
requirements.txt
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
gradio
|
| 2 |
+
librosa
|
| 3 |
+
soundfile
|
| 4 |
+
scipy
|
| 5 |
+
python-dotenv
|
| 6 |
+
pandas
|
| 7 |
+
openpyxl
|
| 8 |
+
google-genai
|
| 9 |
+
pyaudio==0.2.14
|
| 10 |
+
google-generativeai
|
| 11 |
+
openai
|
toolbox/__init__.py
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/python3
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
if __name__ == "__main__":
|
| 6 |
+
pass
|
toolbox/json/__init__.py
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/python3
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
if __name__ == '__main__':
|
| 6 |
+
pass
|
toolbox/json/misc.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/python3
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
from typing import Callable
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
def traverse(js, callback: Callable, *args, **kwargs):
|
| 7 |
+
if isinstance(js, list):
|
| 8 |
+
result = list()
|
| 9 |
+
for l in js:
|
| 10 |
+
l = traverse(l, callback, *args, **kwargs)
|
| 11 |
+
result.append(l)
|
| 12 |
+
return result
|
| 13 |
+
elif isinstance(js, tuple):
|
| 14 |
+
result = list()
|
| 15 |
+
for l in js:
|
| 16 |
+
l = traverse(l, callback, *args, **kwargs)
|
| 17 |
+
result.append(l)
|
| 18 |
+
return tuple(result)
|
| 19 |
+
elif isinstance(js, dict):
|
| 20 |
+
result = dict()
|
| 21 |
+
for k, v in js.items():
|
| 22 |
+
k = traverse(k, callback, *args, **kwargs)
|
| 23 |
+
v = traverse(v, callback, *args, **kwargs)
|
| 24 |
+
result[k] = v
|
| 25 |
+
return result
|
| 26 |
+
elif isinstance(js, int):
|
| 27 |
+
return callback(js, *args, **kwargs)
|
| 28 |
+
elif isinstance(js, str):
|
| 29 |
+
return callback(js, *args, **kwargs)
|
| 30 |
+
else:
|
| 31 |
+
return js
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def demo1():
|
| 35 |
+
d = {
|
| 36 |
+
"env": "ppe",
|
| 37 |
+
"mysql_connect": {
|
| 38 |
+
"host": "$mysql_connect_host",
|
| 39 |
+
"port": 3306,
|
| 40 |
+
"user": "callbot",
|
| 41 |
+
"password": "NxcloudAI2021!",
|
| 42 |
+
"database": "callbot_ppe",
|
| 43 |
+
"charset": "utf8"
|
| 44 |
+
},
|
| 45 |
+
"es_connect": {
|
| 46 |
+
"hosts": ["10.20.251.8"],
|
| 47 |
+
"http_auth": ["elastic", "ElasticAI2021!"],
|
| 48 |
+
"port": 9200
|
| 49 |
+
}
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
def callback(s):
|
| 53 |
+
if isinstance(s, str) and s.startswith('$'):
|
| 54 |
+
return s[1:]
|
| 55 |
+
return s
|
| 56 |
+
|
| 57 |
+
result = traverse(d, callback=callback)
|
| 58 |
+
print(result)
|
| 59 |
+
return
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
if __name__ == '__main__':
|
| 63 |
+
demo1()
|
toolbox/os/__init__.py
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/python3
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
if __name__ == '__main__':
|
| 6 |
+
pass
|
toolbox/os/command.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/python3
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
import os
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class Command(object):
|
| 7 |
+
custom_command = [
|
| 8 |
+
"cd"
|
| 9 |
+
]
|
| 10 |
+
|
| 11 |
+
@staticmethod
|
| 12 |
+
def _get_cmd(command):
|
| 13 |
+
command = str(command).strip()
|
| 14 |
+
if command == "":
|
| 15 |
+
return None
|
| 16 |
+
cmd_and_args = command.split(sep=" ")
|
| 17 |
+
cmd = cmd_and_args[0]
|
| 18 |
+
args = " ".join(cmd_and_args[1:])
|
| 19 |
+
return cmd, args
|
| 20 |
+
|
| 21 |
+
@classmethod
|
| 22 |
+
def popen(cls, command):
|
| 23 |
+
cmd, args = cls._get_cmd(command)
|
| 24 |
+
if cmd in cls.custom_command:
|
| 25 |
+
method = getattr(cls, cmd)
|
| 26 |
+
return method(args)
|
| 27 |
+
else:
|
| 28 |
+
resp = os.popen(command)
|
| 29 |
+
result = resp.read()
|
| 30 |
+
resp.close()
|
| 31 |
+
return result
|
| 32 |
+
|
| 33 |
+
@classmethod
|
| 34 |
+
def cd(cls, args):
|
| 35 |
+
if args.startswith("/"):
|
| 36 |
+
os.chdir(args)
|
| 37 |
+
else:
|
| 38 |
+
pwd = os.getcwd()
|
| 39 |
+
path = os.path.join(pwd, args)
|
| 40 |
+
os.chdir(path)
|
| 41 |
+
|
| 42 |
+
@classmethod
|
| 43 |
+
def system(cls, command):
|
| 44 |
+
return os.system(command)
|
| 45 |
+
|
| 46 |
+
def __init__(self):
|
| 47 |
+
pass
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def ps_ef_grep(keyword: str):
|
| 51 |
+
cmd = "ps -ef | grep {}".format(keyword)
|
| 52 |
+
rows = Command.popen(cmd)
|
| 53 |
+
rows = str(rows).split("\n")
|
| 54 |
+
rows = [row for row in rows if row.__contains__(keyword) and not row.__contains__("grep")]
|
| 55 |
+
return rows
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
if __name__ == "__main__":
|
| 59 |
+
pass
|
toolbox/os/environment.py
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/python3
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
import json
|
| 4 |
+
import os
|
| 5 |
+
|
| 6 |
+
from dotenv import load_dotenv
|
| 7 |
+
from dotenv.main import DotEnv
|
| 8 |
+
|
| 9 |
+
from toolbox.json.misc import traverse
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class EnvironmentManager(object):
|
| 13 |
+
def __init__(self, path, env, override=False):
|
| 14 |
+
filename = os.path.join(path, '{}.env'.format(env))
|
| 15 |
+
self.filename = filename
|
| 16 |
+
|
| 17 |
+
load_dotenv(
|
| 18 |
+
dotenv_path=filename,
|
| 19 |
+
override=override
|
| 20 |
+
)
|
| 21 |
+
|
| 22 |
+
self._environ = dict()
|
| 23 |
+
|
| 24 |
+
def open_dotenv(self, filename: str = None):
|
| 25 |
+
filename = filename or self.filename
|
| 26 |
+
dotenv = DotEnv(
|
| 27 |
+
dotenv_path=filename,
|
| 28 |
+
stream=None,
|
| 29 |
+
verbose=False,
|
| 30 |
+
interpolate=False,
|
| 31 |
+
override=False,
|
| 32 |
+
encoding="utf-8",
|
| 33 |
+
)
|
| 34 |
+
result = dotenv.dict()
|
| 35 |
+
return result
|
| 36 |
+
|
| 37 |
+
def get(self, key, default=None, dtype=str):
|
| 38 |
+
result = os.environ.get(key)
|
| 39 |
+
if result is None:
|
| 40 |
+
if default is None:
|
| 41 |
+
result = None
|
| 42 |
+
else:
|
| 43 |
+
result = default
|
| 44 |
+
else:
|
| 45 |
+
result = dtype(result)
|
| 46 |
+
self._environ[key] = result
|
| 47 |
+
return result
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
_DEFAULT_DTYPE_MAP = {
|
| 51 |
+
'int': int,
|
| 52 |
+
'float': float,
|
| 53 |
+
'str': str,
|
| 54 |
+
'json.loads': json.loads
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
class JsonConfig(object):
|
| 59 |
+
"""
|
| 60 |
+
将 json 中, 形如 `$float:threshold` 的值, 处理为:
|
| 61 |
+
从环境变量中查到 threshold, 再将其转换为 float 类型.
|
| 62 |
+
"""
|
| 63 |
+
def __init__(self, dtype_map: dict = None, environment: EnvironmentManager = None):
|
| 64 |
+
self.dtype_map = dtype_map or _DEFAULT_DTYPE_MAP
|
| 65 |
+
self.environment = environment or os.environ
|
| 66 |
+
|
| 67 |
+
def sanitize_by_filename(self, filename: str):
|
| 68 |
+
with open(filename, 'r', encoding='utf-8') as f:
|
| 69 |
+
js = json.load(f)
|
| 70 |
+
|
| 71 |
+
return self.sanitize_by_json(js)
|
| 72 |
+
|
| 73 |
+
def sanitize_by_json(self, js):
|
| 74 |
+
js = traverse(
|
| 75 |
+
js,
|
| 76 |
+
callback=self.sanitize,
|
| 77 |
+
environment=self.environment
|
| 78 |
+
)
|
| 79 |
+
return js
|
| 80 |
+
|
| 81 |
+
def sanitize(self, string, environment):
|
| 82 |
+
"""支持 $ 符开始的, 环境变量配置"""
|
| 83 |
+
if isinstance(string, str) and string.startswith('$'):
|
| 84 |
+
dtype, key = string[1:].split(':')
|
| 85 |
+
dtype = self.dtype_map[dtype]
|
| 86 |
+
|
| 87 |
+
value = environment.get(key)
|
| 88 |
+
if value is None:
|
| 89 |
+
raise AssertionError('environment not exist. key: {}'.format(key))
|
| 90 |
+
|
| 91 |
+
value = dtype(value)
|
| 92 |
+
result = value
|
| 93 |
+
else:
|
| 94 |
+
result = string
|
| 95 |
+
return result
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
def demo1():
|
| 99 |
+
import json
|
| 100 |
+
|
| 101 |
+
from project_settings import project_path
|
| 102 |
+
|
| 103 |
+
environment = EnvironmentManager(
|
| 104 |
+
path=os.path.join(project_path, 'server/callbot_server/dotenv'),
|
| 105 |
+
env='dev',
|
| 106 |
+
)
|
| 107 |
+
init_scenes = environment.get(key='init_scenes', dtype=json.loads)
|
| 108 |
+
print(init_scenes)
|
| 109 |
+
print(environment._environ)
|
| 110 |
+
return
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
if __name__ == '__main__':
|
| 114 |
+
demo1()
|
toolbox/os/other.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import inspect
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
def pwd():
|
| 6 |
+
"""你在哪个文件调用此函数, 它就会返回那个文件所在的 dir 目标"""
|
| 7 |
+
frame = inspect.stack()[1]
|
| 8 |
+
module = inspect.getmodule(frame[0])
|
| 9 |
+
return os.path.dirname(os.path.abspath(module.__file__))
|