botrun-flow-lang 5.10.291__tar.gz → 5.11.281__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/.env_template +8 -12
  2. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/CHANGELOG.md +30 -0
  3. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/PKG-INFO +1 -1
  4. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/api/line_bot_api.py +148 -5
  5. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/api/storage_api.py +79 -0
  6. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/langgraph_agents/agents/langgraph_react_agent.py +31 -0
  7. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/langgraph_agents/agents/util/local_files.py +77 -3
  8. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/mcp_server/default_mcp.py +37 -13
  9. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/services/storage/storage_cs_store.py +1 -1
  10. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/pyproject.toml +1 -1
  11. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/sh/deploy_bigline.sh +3 -2
  12. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/.gcloudignore +0 -0
  13. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/.gitignore +0 -0
  14. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/.vscode/launch.json +0 -0
  15. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/CLAUDE.md +0 -0
  16. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/Dockerfile.template +0 -0
  17. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/README.md +0 -0
  18. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/__init__.py +0 -0
  19. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/api/__init__.py +0 -0
  20. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/api/auth_api.py +0 -0
  21. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/api/auth_utils.py +0 -0
  22. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/api/botrun_back_api.py +0 -0
  23. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/api/flow_api.py +0 -0
  24. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/api/hatch_api.py +0 -0
  25. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/api/langgraph_api.py +0 -0
  26. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/api/model_api.py +0 -0
  27. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/api/rate_limit_api.py +0 -0
  28. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/api/routes.py +0 -0
  29. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/api/search_api.py +0 -0
  30. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/api/subsidy_api.py +0 -0
  31. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/api/subsidy_api_system_prompt.txt +0 -0
  32. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/api/user_setting_api.py +0 -0
  33. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/api/version_api.py +0 -0
  34. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/api/youtube_api.py +0 -0
  35. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/constants.py +0 -0
  36. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/langgraph_agents/__init__.py +0 -0
  37. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/langgraph_agents/agents/__init__.py +0 -0
  38. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/langgraph_agents/agents/agent_runner.py +0 -0
  39. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/langgraph_agents/agents/agent_tools/__init__.py +0 -0
  40. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/langgraph_agents/agents/agent_tools/step_planner.py +0 -0
  41. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/langgraph_agents/agents/checkpointer/__init__.py +0 -0
  42. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/langgraph_agents/agents/checkpointer/firestore_checkpointer.py +0 -0
  43. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/langgraph_agents/agents/gov_researcher/GOV_RESEARCHER_PRD.md +0 -0
  44. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/langgraph_agents/agents/gov_researcher/__init__.py +0 -0
  45. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/langgraph_agents/agents/gov_researcher/gemini_subsidy_graph.py +0 -0
  46. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/langgraph_agents/agents/gov_researcher/gov_researcher_2_graph.py +0 -0
  47. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/langgraph_agents/agents/gov_researcher/gov_researcher_graph.py +0 -0
  48. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/langgraph_agents/agents/search_agent_graph.py +0 -0
  49. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/langgraph_agents/agents/tools/__init__.py +0 -0
  50. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/langgraph_agents/agents/tools/gemini_code_execution.py +0 -0
  51. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/langgraph_agents/agents/util/__init__.py +0 -0
  52. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/langgraph_agents/agents/util/gemini_grounding.py +0 -0
  53. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/langgraph_agents/agents/util/html_util.py +0 -0
  54. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/langgraph_agents/agents/util/img_util.py +0 -0
  55. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/langgraph_agents/agents/util/mermaid_util.py +0 -0
  56. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/langgraph_agents/agents/util/model_utils.py +0 -0
  57. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/langgraph_agents/agents/util/pdf_analyzer.py +0 -0
  58. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/langgraph_agents/agents/util/perplexity_search.py +0 -0
  59. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/langgraph_agents/agents/util/plotly_util.py +0 -0
  60. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/langgraph_agents/agents/util/tavily_search.py +0 -0
  61. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/langgraph_agents/agents/util/youtube_util.py +0 -0
  62. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/langgraph_agents/cache/__init__.py +0 -0
  63. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/langgraph_agents/cache/langgraph_botrun_cache.py +0 -0
  64. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/llm_agent/__init__.py +0 -0
  65. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/llm_agent/llm_agent.py +0 -0
  66. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/llm_agent/llm_agent_util.py +0 -0
  67. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/log/.gitignore +0 -0
  68. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/main.py +0 -0
  69. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/main_fast.py +0 -0
  70. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/mcp_server/__init__.py +0 -0
  71. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/models/__init__.py +0 -0
  72. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/models/nodes/utils.py +0 -0
  73. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/models/token_usage.py +0 -0
  74. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/requirements.txt +0 -0
  75. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/services/__init__.py +0 -0
  76. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/services/base/firestore_base.py +0 -0
  77. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/services/hatch/__init__.py +0 -0
  78. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/services/hatch/hatch_factory.py +0 -0
  79. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/services/hatch/hatch_fs_store.py +0 -0
  80. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/services/storage/__init__.py +0 -0
  81. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/services/storage/storage_factory.py +0 -0
  82. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/services/storage/storage_store.py +0 -0
  83. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/services/user_setting/__init__.py +0 -0
  84. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/services/user_setting/user_setting_factory.py +0 -0
  85. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/services/user_setting/user_setting_fs_store.py +0 -0
  86. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/static/docs/tools/index.html +0 -0
  87. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/tests/api_functional_tests.py +0 -0
  88. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/tests/api_stress_test.py +0 -0
  89. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/tests/shared_hatch_tests.py +0 -0
  90. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/tests/test_botrun_app.py +0 -0
  91. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/tests/test_files/(/346/272/253/351/246/250/346/210/220/346/236/234 /350/241/214/346/224/277/350/253/213/347/244/272/345/214/257/347/270/275)20250210/345/220/221 /344/270/212/344/272/272/345/240/261/345/221/212/347/260/241/345/240/261 (1).pdf" +0 -0
  92. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/tests/test_files/11206_10808/344/272/272/345/217/243/346/225/270(3/346/256/265/345/271/264/351/275/241/347/265/204+/346/257/224/347/216/207)/345/244/251/344/270/213/351/233/234/350/252/2141.pdf" +0 -0
  93. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/tests/test_files/1120701A/346/265/267/345/273/243/351/233/242/345/262/270/351/242/250/345/212/233/347/231/274/351/233/273/350/250/210/347/225/253/347/222/260/345/242/203/345/275/261/351/237/277/350/252/252/346/230/216/346/233/270-C04.PDF" +0 -0
  94. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/tests/test_files/ImportedPhoto.760363950.029251.jpeg +0 -0
  95. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/tests/test_files/ImportedPhoto.760363950.030446.jpeg +0 -0
  96. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/tests/test_files/ImportedPhoto.760363950.031127.jpeg +0 -0
  97. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/tests/test_files/d5712343.jpg +0 -0
  98. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/tests/test_files/spot_difference_1.png +0 -0
  99. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/tests/test_files/spot_difference_2.png +0 -0
  100. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/tests/test_html_util.py +0 -0
  101. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/tests/test_img_analyzer.py +0 -0
  102. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/tests/test_img_util.py +0 -0
  103. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/tests/test_local_files.py +0 -0
  104. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/tests/test_mermaid_util.py +0 -0
  105. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/tests/test_pdf_analyzer.py +0 -0
  106. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/tests/test_plotly_util.py +0 -0
  107. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/tests/test_run_workflow_engine.py +0 -0
  108. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/tools/generate_docs.py +0 -0
  109. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/tools/templates/tools.html +0 -0
  110. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/utils/__init__.py +0 -0
  111. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/utils/botrun_logger.py +0 -0
  112. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/utils/clients/__init__.py +0 -0
  113. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/utils/clients/rate_limit_client.py +0 -0
  114. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/utils/clients/token_verify_client.py +0 -0
  115. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/utils/google_drive_utils.py +0 -0
  116. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/utils/langchain_utils.py +0 -0
  117. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/botrun_flow_lang/utils/yaml_utils.py +0 -0
  118. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/cloudbuild_template.yaml +0 -0
  119. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/convert_newlines.sh +0 -0
  120. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/deploy.sh +0 -0
  121. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/langgraph.json +0 -0
  122. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/poetry.lock +0 -0
  123. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/poetry.toml +0 -0
  124. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/requirements.txt +0 -0
  125. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/requirements_fast.txt +0 -0
  126. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/sh/deploy_dev.sh +0 -0
  127. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/sh/deploy_evaldev.sh +0 -0
  128. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/sh/deploy_fast.sh +0 -0
  129. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/sh/deploy_modaline.sh +0 -0
  130. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/sh/deploy_prod.sh +0 -0
  131. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/sh/deploy_sebaline.sh +0 -0
  132. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/sh/firestore_checkpointer_delete_thread.py +0 -0
  133. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/sh/generate_docs.sh +0 -0
  134. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/sh/llm_time_test.py +0 -0
  135. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/sh/run_langgraph.py +0 -0
  136. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/sh/run_langgraph_react.py +0 -0
  137. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/sh/run_youtube_summary.py +0 -0
  138. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/sh/scrape_pdf.py +0 -0
  139. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/sh/seba_pypi.sh +0 -0
  140. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/sh/subsidy_test.py +0 -0
  141. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/specs/gov-search/agent-architecture-upgrade.excalidraw +0 -0
  142. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/specs/gov-search/line_dev.excalidraw +0 -0
  143. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/specs/gov-search/subsidy_agent.excalidraw +0 -0
  144. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/specs/gov-search/subsidy_agent_dev.excalidraw +0 -0
  145. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/specs/gov-search/temp_txt.md +0 -0
  146. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/specs/gov-search/temp_txt2.md +0 -0
  147. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/specs/gov-search//346/264/245/350/262/274line/345/276/205/350/250/216/350/253/226.excalidraw" +0 -0
  148. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/specs/system_thinking.excalidraw +0 -0
  149. {botrun_flow_lang-5.10.291 → botrun_flow_lang-5.11.281}/uv.lock +0 -0
@@ -82,15 +82,11 @@ GOOGLE_APPLICATION_CREDENTIALS_FOR_MODELS_SHEET=""
82
82
  # models sheet 的 id
83
83
  MODELS_GSPREAD_ID=""
84
84
 
85
-
86
- # 呼叫 Botrun line auth api 的 token,要使用津貼 line bot 就是必填
87
- BOTRUN_BACK_LINE_AUTH_API_TOKEN=
88
- # [選填] 設定津貼 line bot 在產生美波 url 時要對應的 botrun id,預設值為 波津貼.botrun,要使用津貼 line bot 就是必填
89
- SUBSIDY_LINE_BOT_BOTRUN_ID=波津貼.botrun
90
- # [選填] 設定使用者使用津貼 line bot 產生的美波的 session 多久有效,單位為小時,預設值為 2 小時,要使用津貼 line bot 就是必填
91
- SUBSIDY_LINE_BOT_JWT_TOKEN_HOURS=2
92
- # [選填] 建立津貼 line bot 的使用者建立帳號時的角色,預設值為 member,要使用津貼 line bot 就是必填
93
- SUBSIDY_LINE_BOT_USER_ROLE=member
94
-
95
- # 美波的 url,要使用津貼 line bot 就是必填
96
- BOTRUN_FRONT_URL=
85
+ # subsidy api 相關設定,要使用津貼 line bot 就要設定
86
+ SUBSIDY_API_TOKEN=
87
+ SUBSIDY_API_URL=
88
+
89
+ # subsidy line bot token usage log 相關設定,要使用津貼 line bot 就要設定
90
+ BIGQUERY_TOKEN_LOG_API_URL=
91
+ BIGQUERY_TOKEN_LOG_ENABLED="true"
92
+ SUBSIDY_LINE_BOT_MODEL_NAME="gemini-2.5-pro"
@@ -1,3 +1,33 @@
1
+ ## [5.11.281]
2
+ ### Updated
3
+ - 支援台德 gpt-oss-120b 做為 react agent
4
+ - 要支援的話,需要新增環境變數配置:
5
+ - `TAIDE_API_KEY`
6
+ - `TAIDE_BASE_URL`
7
+
8
+ ## [5.11.41]
9
+ ### Added
10
+ - LINE Bot Token 使用量記錄到 BigQuery 功能
11
+ - 新增環境變數配置:
12
+ - `BIGQUERY_TOKEN_LOG_API_URL`: BigQuery logging API URL
13
+ - `BIGQUERY_TOKEN_LOG_ENABLED`: 是否啟用 token logging(預設為 true)
14
+ - `SUBSIDY_LINE_BOT_MODEL_NAME`: 模型名稱(預設為 gemini-2.0-flash-thinking-exp)
15
+
16
+ ## [5.11.11]
17
+ ### Added
18
+ - DALL-E 圖片永久化儲存功能 [參考 specs/gen-img/design.md]
19
+ - Storage API 新增 `/api/img-files/{user_id}` endpoint,支援圖片永久儲存到 GCS
20
+ - 新增 `_upload_img_file_internal` 內部函數處理圖片上傳
21
+ - local_files.py 新增 `download_image_from_url` 函數從 URL 下載圖片到記憶體
22
+ - local_files.py 新增 `upload_image_and_get_public_url` 函數處理圖片下載與上傳流程
23
+ - MCP generate_image 工具現在會將 DALL-E 生成的圖片自動上傳到 GCS,回傳永久 URL
24
+ - 圖片儲存路徑:`img/{user_id}/dalle_{timestamp}_{random_id}.png`
25
+ - 支援 fallback 機制:上傳失敗時回傳臨時 URL(1小時有效)
26
+
27
+ ### Updated
28
+ - GCS bucket lifecycle rules:將 tmp/ 目錄檔案的自動刪除期限從 7 天延長至 365 天(1年)
29
+ - local_files.py 重新命名 `_perform_upload` → `_perform_tmp_file_upload`,明確表示暫存檔案上傳
30
+
1
31
  ## [5.10.291]
2
32
  ### Updated
3
33
  - line bot api ,呼叫 cbh 做的 api
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: botrun-flow-lang
3
- Version: 5.10.291
3
+ Version: 5.11.281
4
4
  Summary: A flow language for botrun
5
5
  Author-email: sebastian-hsu <sebastian.hsu@gmail.com>
6
6
  License: MIT
@@ -100,6 +100,11 @@ BOTRUN_FRONT_URL = os.getenv("BOTRUN_FRONT_URL", None)
100
100
  SUBSIDY_API_TOKEN = os.getenv("SUBSIDY_API_TOKEN", None)
101
101
  SUBSIDY_API_URL = os.getenv("SUBSIDY_API_URL", "https://p271-subsidy-ie7vwovclq-de.a.run.app/v1/generateContent")
102
102
 
103
+ # BigQuery Token Logging API 相關環境變數
104
+ BIGQUERY_TOKEN_LOG_API_URL = os.getenv("BIGQUERY_TOKEN_LOG_API_URL", "http://localhost:8002/api/v1/logs/text")
105
+ BIGQUERY_TOKEN_LOG_ENABLED = os.getenv("BIGQUERY_TOKEN_LOG_ENABLED", "true").lower() == "true"
106
+ SUBSIDY_LINE_BOT_MODEL_NAME = os.getenv("SUBSIDY_LINE_BOT_MODEL_NAME", "gemini-2.0-flash-thinking-exp")
107
+
103
108
  # 全局變數
104
109
  # 用於追蹤正在處理訊息的使用者,避免同一使用者同時發送多條訊息造成處理衝突
105
110
  _processing_users = set()
@@ -202,6 +207,111 @@ async def log_to_bigquery(
202
207
  )
203
208
 
204
209
 
210
+ async def log_tokens_to_bigquery(
211
+ user_id: str,
212
+ display_name: str,
213
+ log_content: str,
214
+ model: str,
215
+ input_tokens: int | None,
216
+ output_tokens: int | None,
217
+ total_tokens: int | None,
218
+ request: Request,
219
+ session_id: str = "",
220
+ ) -> None:
221
+ """
222
+ 記錄 token 使用量到 BigQuery logging API
223
+
224
+ Args:
225
+ user_id: LINE 使用者 ID
226
+ display_name: 使用者顯示名稱
227
+ log_content: 使用者輸入訊息
228
+ model: 使用的 AI 模型
229
+ input_tokens: 輸入 token 數量
230
+ output_tokens: 輸出 token 數量
231
+ total_tokens: 總 token 數量
232
+ request: FastAPI Request 物件
233
+ session_id: Session ID (可選,預設使用 user_id)
234
+ """
235
+ # 檢查功能是否啟用
236
+ if not BIGQUERY_TOKEN_LOG_ENABLED:
237
+ logging.debug("[Token Logger] BigQuery token logging is disabled")
238
+ return
239
+
240
+ start_time = time.time()
241
+
242
+ try:
243
+ tz = pytz.timezone("Asia/Taipei")
244
+ current_time = datetime.now(tz)
245
+
246
+ # 組裝 payload
247
+ payload = {
248
+ "action_details": log_content,
249
+ "action_type": "call_subsidy_api",
250
+ "botrun": "subsidy_line_bot",
251
+ "dataset_name": os.getenv("BOTRUN_LOG_DATASET_NAME", "subsidy_line_bot"),
252
+ "department": os.getenv("BOTRUN_LOG_DEPARTMENT", "subsidy_line_bot"),
253
+ "developer": "",
254
+ "domain_name": "subsidy_line_bot",
255
+ "input_tokens": input_tokens,
256
+ "model": model,
257
+ "output_tokens": output_tokens,
258
+ "resource_id": json.dumps({
259
+ "user_id": user_id,
260
+ "timestamp": current_time.strftime("%Y-%m-%dT%H:%M:%SZ")
261
+ }),
262
+ "session_id": session_id or user_id,
263
+ "total_tokens": total_tokens,
264
+ "user_agent": request.headers.get("user-agent", "Line Platform"),
265
+ "user_name": display_name
266
+ }
267
+
268
+ logging.info(
269
+ f"[Token Logger] Logging tokens for user {display_name} ({user_id}): "
270
+ f"input={input_tokens}, output={output_tokens}, total={total_tokens}"
271
+ )
272
+
273
+ # 使用 aiohttp 非同步呼叫 API
274
+ timeout = aiohttp.ClientTimeout(total=10) # 10 秒超時
275
+ async with aiohttp.ClientSession(timeout=timeout) as session:
276
+ async with session.post(
277
+ BIGQUERY_TOKEN_LOG_API_URL,
278
+ json=payload,
279
+ headers={"Content-Type": "application/json"}
280
+ ) as response:
281
+ response_text = await response.text()
282
+
283
+ if response.status == 200:
284
+ elapsed_time = time.time() - start_time
285
+ logging.info(
286
+ f"[Token Logger] Successfully logged tokens to BigQuery for user "
287
+ f"{display_name} ({user_id}), elapsed time: {elapsed_time:.3f}s"
288
+ )
289
+ else:
290
+ logging.error(
291
+ f"[Token Logger] Failed to log tokens, API returned status {response.status}: {response_text}"
292
+ )
293
+
294
+ except asyncio.TimeoutError:
295
+ elapsed_time = time.time() - start_time
296
+ logging.error(
297
+ f"[Token Logger] Timeout while logging tokens for user {display_name} ({user_id}), "
298
+ f"elapsed time: {elapsed_time:.3f}s"
299
+ )
300
+ except aiohttp.ClientError as e:
301
+ elapsed_time = time.time() - start_time
302
+ logging.error(
303
+ f"[Token Logger] Network error while logging tokens for user {display_name} ({user_id}): {e}, "
304
+ f"elapsed time: {elapsed_time:.3f}s"
305
+ )
306
+ except Exception as e:
307
+ elapsed_time = time.time() - start_time
308
+ logging.error(
309
+ f"[Token Logger] Unexpected error while logging tokens for user {display_name} ({user_id}): {e}, "
310
+ f"elapsed time: {elapsed_time:.3f}s"
311
+ )
312
+ traceback.print_exc()
313
+
314
+
205
315
  def get_prompt_from_google_doc(tag_name: str, fallback_prompt: str = ""):
206
316
  """
207
317
  從 Google 文件中提取指定標籤的內容
@@ -736,13 +846,37 @@ async def handle_message(
736
846
  _processing_users.add(user_id)
737
847
 
738
848
  try:
739
- reply_text, related_questions = await get_reply_text(
849
+ reply_text, related_questions, usage_metadata = await get_reply_text(
740
850
  user_message, user_id, display_name, request
741
851
  )
742
852
  logging.info(
743
853
  f"[Line Bot Webhook: handle_message] Total response length: {len(reply_text)}"
744
854
  )
745
855
 
856
+ # 記錄 token 使用量到 BigQuery (非阻塞式)
857
+ if usage_metadata:
858
+ # 把 user_message 跟 reply_text 合併成 json 格式紀錄
859
+ log_content = json.dumps(
860
+ {
861
+ "user_message": user_message,
862
+ "reply_text": reply_text
863
+ },
864
+ ensure_ascii=False
865
+ )
866
+ asyncio.create_task(
867
+ log_tokens_to_bigquery(
868
+ user_id=user_id,
869
+ display_name=display_name,
870
+ log_content=log_content,
871
+ model=usage_metadata.get("model", SUBSIDY_LINE_BOT_MODEL_NAME),
872
+ input_tokens=usage_metadata.get("promptTokenCount", None),
873
+ output_tokens=usage_metadata.get("candidatesTokenCount", None),
874
+ total_tokens=usage_metadata.get("totalTokenCount", None),
875
+ request=request,
876
+ session_id=user_id,
877
+ )
878
+ )
879
+
746
880
  # 將長訊息分段,每段不超過 LINE_MAX_MESSAGE_LENGTH
747
881
  message_chunks = []
748
882
  remaining_text = reply_text
@@ -934,7 +1068,7 @@ async def get_reply_text(
934
1068
  user_id: str,
935
1069
  display_name: str,
936
1070
  request: Request,
937
- ) -> tuple[str, list]:
1071
+ ) -> tuple[str, list, dict]:
938
1072
  """
939
1073
  使用外部 API 處理使用者訊息並回傳回覆內容
940
1074
 
@@ -945,7 +1079,7 @@ async def get_reply_text(
945
1079
  request (Request): FastAPI request 物件,用於記錄到 BigQuery
946
1080
 
947
1081
  Returns:
948
- tuple[str, list]: 包含回覆訊息和相關問題的元組
1082
+ tuple[str, list, dict]: 包含回覆訊息、相關問題和 token 使用量的元組
949
1083
  """
950
1084
  start_time = time.time()
951
1085
 
@@ -961,6 +1095,15 @@ async def get_reply_text(
961
1095
  system_instruction=system_instruction
962
1096
  )
963
1097
 
1098
+ # 提取 token 使用量資訊
1099
+ usage_metadata = api_response.get("usageMetadata", {})
1100
+ logging.info(
1101
+ f"[Line Bot Webhook: get_reply_text] Token usage: "
1102
+ f"input={usage_metadata.get('promptTokenCount', 0)}, "
1103
+ f"output={usage_metadata.get('candidatesTokenCount', 0)}, "
1104
+ f"total={usage_metadata.get('totalTokenCount', 0)}"
1105
+ )
1106
+
964
1107
  # 從 API 回應中提取文字內容
965
1108
  full_response = ""
966
1109
  if "candidates" in api_response and len(api_response["candidates"]) > 0:
@@ -1003,7 +1146,7 @@ async def get_reply_text(
1003
1146
  f"[Line Bot Webhook: get_reply_text] total took {time.time() - start_time:.3f}s"
1004
1147
  )
1005
1148
 
1006
- return full_response, related_questions
1149
+ return full_response, related_questions, usage_metadata
1007
1150
 
1008
1151
  except Exception as e:
1009
1152
  import traceback
@@ -1012,7 +1155,7 @@ async def get_reply_text(
1012
1155
 
1013
1156
  # 返回錯誤訊息
1014
1157
  error_message = "抱歉,處理您的訊息時遇到問題,請稍後再試。"
1015
- return error_message, []
1158
+ return error_message, [], {}
1016
1159
 
1017
1160
 
1018
1161
  async def handle_feedback(
@@ -202,6 +202,43 @@ async def _upload_html_file_internal(
202
202
  return public_url
203
203
 
204
204
 
205
+ async def _upload_img_file_internal(
206
+ user_id: str, file_content: bytes, file_name: str, content_type: str = "image/png"
207
+ ) -> str:
208
+ """
209
+ Internal function to upload image file to GCS
210
+
211
+ Args:
212
+ user_id: User ID
213
+ file_content: File content as bytes
214
+ file_name: File name
215
+ content_type: MIME type of the file
216
+
217
+ Returns:
218
+ str: Public URL of the uploaded file
219
+
220
+ Raises:
221
+ Exception: If upload fails
222
+ """
223
+ storage = storage_store_factory()
224
+
225
+ # Create file object from bytes
226
+ file_object = BytesIO(file_content)
227
+
228
+ # Build storage path - use img directory for permanent storage
229
+ storage_path = f"img/{user_id}/{file_name}"
230
+
231
+ # Store file with public access and content type
232
+ success, public_url = await storage.store_file(
233
+ storage_path, file_object, public=True, content_type=content_type
234
+ )
235
+
236
+ if not success:
237
+ raise Exception("Failed to store image file")
238
+
239
+ return public_url
240
+
241
+
205
242
  @router.post("/html-files/{user_id}")
206
243
  async def upload_html_file(
207
244
  user_id: str,
@@ -244,6 +281,48 @@ async def upload_html_file(
244
281
  raise HTTPException(status_code=500, detail=str(e))
245
282
 
246
283
 
284
+ @router.post("/img-files/{user_id}")
285
+ async def upload_img_file(
286
+ user_id: str,
287
+ file: FastAPIUploadFile = File(...),
288
+ file_name: str = Form(...),
289
+ content_type: str = Form(None),
290
+ # current_user: CurrentUser = Depends(verify_jwt_token)
291
+ ) -> dict:
292
+ """
293
+ 儲存圖片檔案到 GCS,檔案會是公開可存取且永久保存
294
+
295
+ Args:
296
+ user_id: 使用者 ID
297
+ file: 上傳的檔案
298
+ file_name: 檔案名稱
299
+ content_type: 檔案的 MIME type,如果沒有提供則使用檔案的 content_type
300
+ """
301
+ # Verify user permission
302
+ # verify_user_permission(current_user, user_id)
303
+
304
+ try:
305
+ # 讀取上傳的檔案內容
306
+ contents = await file.read()
307
+
308
+ # 如果沒有提供 content_type,使用檔案的 content_type
309
+ if not content_type:
310
+ content_type = file.content_type
311
+
312
+ # Use internal function to upload file
313
+ public_url = await _upload_img_file_internal(
314
+ user_id, contents, file_name, content_type
315
+ )
316
+
317
+ return {
318
+ "message": "Image file uploaded successfully",
319
+ "success": True,
320
+ "url": public_url,
321
+ }
322
+ except Exception as e:
323
+ raise HTTPException(status_code=500, detail=str(e))
324
+
325
+
247
326
  @router.get("/directory-sizes")
248
327
  async def get_directory_sizes(current_user: CurrentUser = Depends(verify_jwt_token)):
249
328
  """
@@ -142,11 +142,42 @@ def get_react_agent_model_name(model_name: str = ""):
142
142
 
143
143
  ANTHROPIC_MAX_TOKENS = 64000
144
144
  GEMINI_MAX_TOKENS = 32000
145
+ TAIDE_MAX_TOKENS = 8192
145
146
 
146
147
 
147
148
  def get_react_agent_model(model_name: str = ""):
148
149
  final_model_name = get_react_agent_model_name(model_name).strip()
149
150
 
151
+ # 處理 taide/ 前綴的模型
152
+ if final_model_name.startswith("taide/"):
153
+ taide_api_key = os.getenv("TAIDE_API_KEY", "")
154
+ taide_base_url = os.getenv("TAIDE_BASE_URL", "")
155
+
156
+ if not taide_api_key or not taide_base_url:
157
+ raise ValueError(
158
+ f"Model name starts with 'taide/' but TAIDE_API_KEY or TAIDE_BASE_URL not set. "
159
+ f"Both environment variables are required for: {final_model_name}"
160
+ )
161
+
162
+ # 取得 taide/ 後面的模型名稱
163
+ taide_model_name = final_model_name[len("taide/"):]
164
+
165
+ if not taide_model_name:
166
+ raise ValueError(
167
+ f"Invalid taide model format: {final_model_name}. "
168
+ "Expected format: taide/<model_name>"
169
+ )
170
+
171
+ model = ChatOpenAI(
172
+ openai_api_key=taide_api_key,
173
+ openai_api_base=taide_base_url,
174
+ model_name=taide_model_name,
175
+ temperature=0,
176
+ max_tokens=TAIDE_MAX_TOKENS,
177
+ )
178
+ logger.info(f"model ChatOpenAI (TAIDE) {taide_model_name} @ {taide_base_url}")
179
+ return model
180
+
150
181
  # 處理 vertexai/ 前綴的模型
151
182
  if final_model_name.startswith("vertex-ai/"):
152
183
  vertex_project = os.getenv("VERTEX_AI_LANGCHAIN_PROJECT", "")
@@ -52,7 +52,7 @@ def upload_and_get_tmp_public_url(
52
52
  """
53
53
  # 外層 try-except: 處理第一次嘗試與重試邏輯
54
54
  try:
55
- return _perform_upload(file_path, botrun_flow_lang_url, user_id)
55
+ return _perform_tmp_file_upload(file_path, botrun_flow_lang_url, user_id)
56
56
  except Exception as e:
57
57
  import traceback
58
58
 
@@ -68,7 +68,7 @@ def upload_and_get_tmp_public_url(
68
68
  # 第二次嘗試
69
69
  try:
70
70
  print("Retry attempt...")
71
- return _perform_upload(file_path, botrun_flow_lang_url, user_id)
71
+ return _perform_tmp_file_upload(file_path, botrun_flow_lang_url, user_id)
72
72
  except Exception as retry_e:
73
73
  # 第二次嘗試也失敗,記錄錯誤並返回錯誤訊息
74
74
  print(f"Retry attempt failed: {str(retry_e)}")
@@ -76,7 +76,7 @@ def upload_and_get_tmp_public_url(
76
76
  return "Error uploading file"
77
77
 
78
78
 
79
- def _perform_upload(
79
+ def _perform_tmp_file_upload(
80
80
  file_path: str, botrun_flow_lang_url: str = "", user_id: str = ""
81
81
  ) -> str:
82
82
  """執行實際的上傳操作
@@ -343,3 +343,77 @@ async def _perform_html_upload(
343
343
  traceback.print_exc()
344
344
  # 這裡把異常往上拋,讓外層的重試邏輯處理
345
345
  raise e
346
+
347
+
348
+ async def download_image_from_url(image_url: str) -> tuple[BytesIO, str]:
349
+ """
350
+ 從 URL 下載圖片到記憶體
351
+
352
+ Args:
353
+ image_url: 圖片的 URL
354
+
355
+ Returns:
356
+ tuple[BytesIO, str]: (圖片內容, content_type)
357
+
358
+ Raises:
359
+ Exception: 下載失敗時拋出例外
360
+ """
361
+ try:
362
+ import httpx
363
+
364
+ async with httpx.AsyncClient(timeout=30.0) as client:
365
+ response = await client.get(image_url, follow_redirects=True)
366
+ response.raise_for_status()
367
+
368
+ # 從 response headers 取得 content type
369
+ content_type = response.headers.get("content-type", "image/png")
370
+
371
+ # 將圖片內容存入 BytesIO
372
+ image_data = BytesIO(response.content)
373
+
374
+ return image_data, content_type
375
+
376
+ except Exception as e:
377
+ raise Exception(f"Failed to download image from URL: {str(e)}")
378
+
379
+
380
+ async def upload_image_and_get_public_url(
381
+ image_url: str, botrun_flow_lang_url: str = "", user_id: str = ""
382
+ ) -> str:
383
+ """
384
+ 從 URL 下載圖片並上傳到 GCS /img 目錄,取得永久公開 URL
385
+
386
+ Args:
387
+ image_url: 圖片來源 URL(如 DALL-E 生成的臨時 URL)
388
+ botrun_flow_lang_url: botrun flow lang API 的 URL
389
+ user_id: 使用者 ID
390
+
391
+ Returns:
392
+ str: 上傳後的永久公開存取 URL
393
+ """
394
+ try:
395
+ # 1. 從 URL 下載圖片
396
+ image_data, content_type = await download_image_from_url(image_url)
397
+
398
+ # 2. 生成唯一檔案名稱
399
+ import uuid
400
+ from datetime import datetime
401
+
402
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
403
+ file_name = f"dalle_{timestamp}_{uuid.uuid4().hex[:8]}.png"
404
+
405
+ # 3. 使用內部函數上傳
406
+ from botrun_flow_lang.api.storage_api import _upload_img_file_internal
407
+
408
+ public_url = await _upload_img_file_internal(
409
+ user_id, image_data.getvalue(), file_name, content_type
410
+ )
411
+
412
+ return public_url
413
+
414
+ except Exception as e:
415
+ import traceback
416
+
417
+ print(f"Error uploading image: {str(e)}")
418
+ traceback.print_exc()
419
+ raise e
@@ -128,9 +128,11 @@ async def chat_with_imgs(
128
128
 
129
129
 
130
130
  @mcp.tool()
131
- async def generate_image(user_input: str, user_id: str = "") -> str:
131
+ async def generate_image(
132
+ user_input: str, user_id: str = "", botrun_flow_lang_url: str = ""
133
+ ) -> str:
132
134
  """
133
- Generate high-quality images using DALL-E 3.
135
+ Generate high-quality images using DALL-E 3 and store permanently in GCS.
134
136
 
135
137
  When using generate_image tool, you must include the image URL in your response.
136
138
  You MUST respond using this format (from @begin img to @end, including the image URL):
@@ -161,17 +163,22 @@ async def generate_image(user_input: str, user_id: str = "") -> str:
161
163
  Args:
162
164
  user_input: Detailed description of the image you want to generate.
163
165
  Be specific about style, content, and composition.
164
- user_id: REQUIRED - User ID for rate limit checking (LLM can get this from system prompt)
166
+ user_id: REQUIRED - User ID for rate limit and file storage (LLM can get this from system prompt)
167
+ botrun_flow_lang_url: REQUIRED - URL for the botrun flow lang API (LLM can get this from system prompt)
165
168
 
166
169
  Returns:
167
- str: URL to the generated image, or error message if generation fails
170
+ str: Permanent URL to the generated image stored in GCS, or error message if generation fails
168
171
  """
169
172
  try:
170
173
  logger.info(f"generate_image user_input: {user_input}")
171
174
 
175
+ # 驗證必要參數
172
176
  if not user_id:
173
- logger.error("User ID not available for rate limit check")
174
- raise Exception("User ID not available for rate limit check")
177
+ logger.error("User ID not available")
178
+ raise Exception("User ID not available")
179
+ if not botrun_flow_lang_url:
180
+ logger.error("botrun_flow_lang_url not available")
181
+ raise Exception("botrun_flow_lang_url not available")
175
182
 
176
183
  # Check rate limit before generating image
177
184
  rate_limit_client = RateLimitClient()
@@ -193,25 +200,42 @@ async def generate_image(user_input: str, user_id: str = "") -> str:
193
200
  f"Current usage: {current_usage}. Please try again tomorrow."
194
201
  )
195
202
 
196
- # Proceed with image generation using DALL-E API Wrapper
203
+ # 2. 使用 DALL-E 生成圖片
197
204
  dalle_wrapper = DallEAPIWrapper(
198
205
  api_key=os.getenv("OPENAI_API_KEY"), model="dall-e-3"
199
206
  )
200
207
 
201
208
  # Generate image with token usage tracking
202
209
  with get_openai_callback() as cb:
203
- image_url = dalle_wrapper.run(user_input)
210
+ temp_image_url = dalle_wrapper.run(user_input)
204
211
  logger.info(
205
- f"generate_image=======> Estimated prompt tokens: {cb.prompt_tokens}, "
212
+ f"DALL-E generated temporary URL: {temp_image_url}, "
213
+ f"prompt tokens: {cb.prompt_tokens}, "
206
214
  f"completion tokens: {cb.completion_tokens}"
207
215
  )
208
216
 
209
- logger.info(f"generate_image generated============> {image_url}")
217
+ # 3. 下載並上傳到 GCS,取得永久 URL
218
+ from botrun_flow_lang.langgraph_agents.agents.util.local_files import (
219
+ upload_image_and_get_public_url,
220
+ )
210
221
 
211
- # Update usage counter after successful generation
212
- await rate_limit_client.update_drawing_usage(user_id)
222
+ try:
223
+ permanent_url = await upload_image_and_get_public_url(
224
+ temp_image_url, botrun_flow_lang_url, user_id
225
+ )
226
+ logger.info(f"Image uploaded to GCS: {permanent_url}")
213
227
 
214
- return image_url
228
+ # 4. 更新使用計數
229
+ await rate_limit_client.update_drawing_usage(user_id)
230
+
231
+ return permanent_url
232
+ except Exception as upload_error:
233
+ logger.error(
234
+ f"Failed to upload to GCS, returning temporary URL: {upload_error}"
235
+ )
236
+ # Fallback: 回傳臨時 URL
237
+ await rate_limit_client.update_drawing_usage(user_id)
238
+ return temp_image_url
215
239
 
216
240
  except Exception as e:
217
241
  logger.error(f"generate_image error: {e}", error=str(e), exc_info=True)
@@ -38,7 +38,7 @@ class StorageCsStore(StorageStore):
38
38
  desired_rules = [
39
39
  {
40
40
  "action": {"type": "Delete"},
41
- "condition": {"age": 7, "matchesPrefix": ["tmp/"]},
41
+ "condition": {"age": 365, "matchesPrefix": ["tmp/"]},
42
42
  }
43
43
  ]
44
44
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "botrun-flow-lang"
3
- version = "5.10.291"
3
+ version = "5.11.281"
4
4
  description = "A flow language for botrun"
5
5
  authors = [
6
6
  { name = "sebastian-hsu", email = "sebastian.hsu@gmail.com" }
@@ -26,8 +26,9 @@ gcloud alpha run deploy botrun-flow-lang-fastapi-bigline \
26
26
  --cpu 2 \
27
27
  --memory 8Gi \
28
28
  --min-instances 0 \
29
- --max-instances 5 \
29
+ --max-instances 1 \
30
30
  --timeout 3600s \
31
31
  --concurrency 300 \
32
- --cpu-boost
32
+ --cpu-boost \
33
+ --labels=cloud-run=botrun-flow-lang-fastapi-bigline,name=p271-subsidy-line-bot
33
34
  # 如果冷啟動不是問題,應該要改成 gen2或是 default