Download app.py from doropiza/MCP_test: direct link, hf CLI and curl.
- Browser
- Download file 6.02 kB
-
https://huggingface.co/spaces/doropiza/MCP_test/resolve/main/app.py
- Command line
-
hf download hf://spaces/doropiza/MCP_test/app.py
-
curl -L -o app.py https://huggingface.co/spaces/doropiza/MCP_test/resolve/main/app.py
6.02 kB
| import os | |
| from typing import Any, Dict, List, Tuple | |
| import gradio as gr | |
| from openai import OpenAI | |
| def get_openai_client() -> OpenAI: | |
| """OpenAI API クライアントを環境変数から初期化して返す。""" | |
| api_key = os.getenv("OPENAI_API_KEY", "").strip() | |
| if not api_key: | |
| raise RuntimeError("OPENAI_API_KEY が設定されていません。環境変数を確認してください。") | |
| base_url = os.getenv("OPENAI_API_BASE", "").strip() or None | |
| return OpenAI(api_key=api_key, base_url=base_url) | |
| def get_vector_store_id() -> str: | |
| """環境変数 VECTOR_STORE_ID から単一のベクターストアIDを取得する。""" | |
| value = os.getenv("VECTOR_STORE_ID", "").strip() | |
| if not value: | |
| raise RuntimeError("VECTOR_STORE_ID が設定されていません。環境変数を確認してください。") | |
| return value.split(",")[0].strip() | |
| def extract_citations(response_dict: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """Responses API レスポンスから file_search の引用情報を抽出する。""" | |
| citations: List[Dict[str, Any]] = [] | |
| outputs = response_dict.get("output") or [] | |
| for output in outputs: | |
| for content in output.get("content", []) or []: | |
| annotations = content.get("annotations") or [] | |
| for annotation in annotations: | |
| file_citation = annotation.get("file_citation") | |
| if not file_citation: | |
| continue | |
| entry = { | |
| "file_id": file_citation.get("file_id"), | |
| "file_name": file_citation.get("file_name"), | |
| "vector_store_id": file_citation.get("vector_store_id"), | |
| "quote": file_citation.get("quote"), | |
| } | |
| if entry not in citations: | |
| citations.append(entry) | |
| return citations | |
| def answer_with_file_search( | |
| vector_store_id: str, | |
| query: str, | |
| top_k: int = 5, | |
| model: str = "gpt-4o-mini", | |
| ) -> Tuple[str, List[Dict[str, Any]]]: | |
| """ | |
| Responses API + file_search ツールを用いて検索と回答を同時に行い、回答と引用を返す。 | |
| Cookbook の "Integrating search results with LLM in a single API call" を参考に構成。 | |
| """ | |
| if not query.strip(): | |
| return "", [] | |
| client = get_openai_client() | |
| system_prompt = ( | |
| "あなたは取得したドキュメントから回答するアシスタントです。" | |
| ) | |
| user_message = f"{query}\n\n検索結果は最大 {top_k} 件まで引用してください。" | |
| response = client.responses.create( | |
| model=model, | |
| input=[ | |
| {"role": "system", "content": system_prompt}, | |
| {"role": "user", "content": user_message}, | |
| ], | |
| tools=[ | |
| { | |
| "type": "file_search", | |
| "vector_store_ids": [vector_store_id], | |
| } | |
| ], | |
| ) | |
| try: | |
| response_dict = response.model_dump() | |
| except AttributeError: | |
| # 旧バージョンSDK互換: model_dump が無ければ辞書化 fallback | |
| response_dict = getattr(response, "to_dict", lambda: {})() | |
| answer_text = response_dict.get("output_text") or "" | |
| citations = extract_citations(response_dict) | |
| return answer_text.strip(), citations | |
| def format_answer(answer: str, citations: List[Dict[str, Any]]) -> str: | |
| """回答文と引用リストを UI 向けのテキストに整形する。""" | |
| if not answer: | |
| return "該当データがありません!" | |
| lines = [answer] | |
| if citations: | |
| lines.append("") | |
| lines.append("引用:") | |
| for citation in citations: | |
| file_name = citation.get("file_name") or citation.get("file_id") or "unknown" | |
| quote = citation.get("quote") | |
| if quote: | |
| lines.append(f"- {quote} [{file_name}]") | |
| else: | |
| lines.append(f"- [{file_name}]") | |
| return "\n".join(lines) | |
| def produce_answer(question: str, top_k: int) -> str: | |
| """Gradio UI からの質問を処理し、Responses API の結果を返す。""" | |
| question = (question or "").strip() | |
| if not question: | |
| return "質問が入力されていません。" | |
| try: | |
| vector_store_id = get_vector_store_id() | |
| answer, citations = answer_with_file_search(vector_store_id, question, top_k=top_k) | |
| return format_answer(answer, citations) | |
| except RuntimeError as runtime_error: | |
| return str(runtime_error) | |
| except Exception: | |
| return "システムエラーが発生しました。時間をおいて再度お試しください。" | |
| def respond(question: str, top_k: int = 5) -> str: | |
| """Gradio の UI から呼び出されるハンドラ。""" | |
| try: | |
| return produce_answer(question, int(top_k)) | |
| except Exception: | |
| return "システムエラーが発生しました。時間をおいて再度お試しください。" | |
| with gr.Blocks() as demo: | |
| gr.Markdown( | |
| """ | |
| ## MCP Storage レスポンダ | |
| OpenAI Responses API の file_search ツールを利用し、指定した Vector Store から根拠付きで回答します。 | |
| OPENAI_API_KEY と VECTOR_STORE_ID を環境変数に設定してからご利用ください。 | |
| """ | |
| ) | |
| with gr.Row(): | |
| question_box = gr.Textbox( | |
| label="質問", | |
| placeholder="ここに質問を入力してください(例: 製品Aの対応OSは?)", | |
| lines=4, | |
| ) | |
| with gr.Row(): | |
| top_k_slider = gr.Slider( | |
| minimum=1, | |
| maximum=10, | |
| value=5, | |
| step=1, | |
| label="検索件数 (top_k)", | |
| ) | |
| with gr.Row(): | |
| output_box = gr.Textbox(label="回答", lines=10) | |
| with gr.Row(): | |
| submit_button = gr.Button("検索") | |
| submit_button.click(respond, inputs=[question_box, top_k_slider], outputs=output_box) | |
| if __name__ == "__main__": | |
| demo.launch() | |