downshift 0.1.0.dev0__tar.gz → 0.1.1__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 (285) hide show
  1. downshift-0.1.1/.github/workflows/ci.yml +53 -0
  2. downshift-0.1.1/.github/workflows/cost-diff.yml +20 -0
  3. {downshift-0.1.0.dev0 → downshift-0.1.1}/.github/workflows/publish.yml +2 -2
  4. downshift-0.1.1/CHANGELOG.md +60 -0
  5. downshift-0.1.1/PKG-INFO +224 -0
  6. downshift-0.1.1/README.md +181 -0
  7. downshift-0.1.1/action.yml +117 -0
  8. downshift-0.1.1/bob_sessions/downshift_task07_apply3b.png +0 -0
  9. downshift-0.1.1/bob_sessions/downshift_task08_review.png +0 -0
  10. downshift-0.1.1/bob_sessions/downshift_task09_web01.png +0 -0
  11. downshift-0.1.1/bob_sessions/downshift_task09_web02.png +0 -0
  12. downshift-0.1.1/bob_sessions/downshift_task09_web03.png +0 -0
  13. downshift-0.1.1/bob_sessions/downshift_task10_case_study_a.png +0 -0
  14. downshift-0.1.1/bob_sessions/downshift_task10_case_study_b.png +0 -0
  15. downshift-0.1.1/bob_sessions/downshift_task10_case_study_c.png +0 -0
  16. downshift-0.1.1/bob_sessions/downshift_task10_case_study_d.png +0 -0
  17. downshift-0.1.1/bob_sessions/downshift_task11_mem0_a.png +0 -0
  18. downshift-0.1.1/bob_sessions/downshift_task11_mem0_b.png +0 -0
  19. downshift-0.1.1/bob_sessions/downshift_task11_mem0_c.png +0 -0
  20. downshift-0.1.1/bob_sessions/downshift_task11_mem0_d.png +0 -0
  21. downshift-0.1.1/bob_sessions/downshift_task11_mem0_e.png +0 -0
  22. downshift-0.1.1/bob_sessions/downshift_task12_payback_a.png +0 -0
  23. downshift-0.1.1/bob_sessions/downshift_task12_payback_b.png +0 -0
  24. downshift-0.1.1/bob_sessions/downshift_task12_payback_c.png +0 -0
  25. downshift-0.1.1/bob_sessions/downshift_task12_payback_d.png +0 -0
  26. downshift-0.1.1/bob_sessions/downshift_task12_payback_e.png +0 -0
  27. downshift-0.1.1/bob_sessions/downshift_task12_payback_f.png +0 -0
  28. downshift-0.1.1/bob_sessions/downshift_task12_payback_g.png +0 -0
  29. downshift-0.1.1/bob_sessions/downshift_task12_payback_h.png +0 -0
  30. downshift-0.1.1/bob_sessions/downshift_task12_payback_i.png +0 -0
  31. downshift-0.1.1/bob_sessions/downshift_task13_review_a.png +0 -0
  32. downshift-0.1.1/bob_sessions/downshift_task13_review_b.png +0 -0
  33. downshift-0.1.1/bob_sessions/downshift_task13_review_c.png +0 -0
  34. downshift-0.1.1/bob_sessions/downshift_task13_review_d.png +0 -0
  35. downshift-0.1.1/bob_sessions/downshift_task13_review_e.png +0 -0
  36. downshift-0.1.1/bob_sessions/downshift_task13_review_f.png +0 -0
  37. downshift-0.1.1/bob_sessions/downshift_task13_review_g.png +0 -0
  38. downshift-0.1.1/bob_sessions/downshift_task14_mem0_evals_a.png +0 -0
  39. downshift-0.1.1/bob_sessions/downshift_task14_mem0_evals_b.png +0 -0
  40. downshift-0.1.1/bob_sessions/downshift_task14_mem0_evals_skill.png +0 -0
  41. downshift-0.1.1/bob_sessions/downshift_task15_docs_a01.png +0 -0
  42. downshift-0.1.1/bob_sessions/downshift_task15_docs_a02.png +0 -0
  43. downshift-0.1.1/bob_sessions/downshift_task15_docs_b.png +0 -0
  44. downshift-0.1.1/bob_sessions/downshift_task15_docs_c.png +0 -0
  45. downshift-0.1.1/bob_sessions/downshift_task16_review_a.png +0 -0
  46. downshift-0.1.1/bob_sessions/downshift_task16_review_b.png +0 -0
  47. downshift-0.1.1/bob_sessions/downshift_task16_review_c.png +0 -0
  48. downshift-0.1.1/bob_sessions/downshift_task16_review_exclusions.png +0 -0
  49. downshift-0.1.1/bob_sessions/downshift_task17_commit_msg.png +0 -0
  50. downshift-0.1.1/docs/case-study/mem0/downshift.audit.json +2024 -0
  51. downshift-0.1.1/docs/case-study/mem0/downshift.local.yaml +38 -0
  52. downshift-0.1.1/docs/case-study/mem0/downshift.scan.json +1989 -0
  53. downshift-0.1.1/docs/case-study/mem0/downshift.yaml +33 -0
  54. downshift-0.1.1/docs/case-study/mem0/estimate.md +20 -0
  55. downshift-0.1.1/docs/case-study/mem0/eval-report.md +97 -0
  56. downshift-0.1.1/docs/case-study/mem0/evals/mem0.memory.main__Memory._add_to_vector_store.jsonl +29 -0
  57. downshift-0.1.1/docs/case-study/mem0/evals/mem0.memory.main__Memory._create_procedural_memory.jsonl +12 -0
  58. downshift-0.1.1/docs/case-study/mem0/evals/mem0.reranker.llm_reranker__LLMReranker.rerank.jsonl +24 -0
  59. downshift-0.1.1/docs/case-study/mem0/evals-brief.md +47 -0
  60. downshift-0.1.1/docs/case-study/mem0/results/mem0.reranker.llm_reranker__LLMReranker.rerank/qwen2.5-0.5b.jsonl +24 -0
  61. downshift-0.1.1/docs/case-study/mem0/results/mem0.reranker.llm_reranker__LLMReranker.rerank/qwen2.5-1.5b.jsonl +24 -0
  62. downshift-0.1.1/docs/case-study/mem0/results/mem0.reranker.llm_reranker__LLMReranker.rerank/qwen2.5-3b.jsonl +24 -0
  63. downshift-0.1.1/docs/case-study/mem0/results/mem0.reranker.llm_reranker__LLMReranker.rerank/qwen2.5-7b.jsonl +24 -0
  64. downshift-0.1.1/docs/case-study/orchestrai/downshift.audit.json +245 -0
  65. downshift-0.1.1/docs/case-study/orchestrai/downshift.scan.json +54 -0
  66. downshift-0.1.1/docs/case-study/orchestrai/downshift.yaml +24 -0
  67. downshift-0.1.1/docs/case-study/orchestrai/estimate.md +20 -0
  68. downshift-0.1.1/docs/case-study-mem0.md +169 -0
  69. downshift-0.1.1/docs/case-study.md +112 -0
  70. downshift-0.1.1/docs/images/bad_pr_cost_diff_a.png +0 -0
  71. downshift-0.1.1/docs/images/bad_pr_cost_diff_b.png +0 -0
  72. downshift-0.1.1/docs/specs/audit-payback-plan.md +377 -0
  73. downshift-0.1.1/docs/specs/audit-payback.md +91 -0
  74. {downshift-0.1.0.dev0 → downshift-0.1.1}/examples/supportdesk/README.md +2 -2
  75. {downshift-0.1.0.dev0 → downshift-0.1.1}/examples/supportdesk/downshift.report.md +40 -19
  76. {downshift-0.1.0.dev0 → downshift-0.1.1}/examples/supportdesk/downshift.yaml +2 -1
  77. {downshift-0.1.0.dev0 → downshift-0.1.1}/examples/supportdesk/models.yaml +4 -3
  78. downshift-0.1.1/examples/supportdesk/results/supportdesk.agent_assist__draft_reply/qwen2.5-3b.jsonl +22 -0
  79. downshift-0.1.1/examples/supportdesk/results/supportdesk.agent_assist__summarize_for_agent/qwen2.5-3b.jsonl +22 -0
  80. downshift-0.1.1/examples/supportdesk/results/supportdesk.extract__extract_order_info/qwen2.5-3b.jsonl +21 -0
  81. downshift-0.1.1/examples/supportdesk/results/supportdesk.misc_utils__lang_of/qwen2.5-3b.jsonl +22 -0
  82. downshift-0.1.1/examples/supportdesk/results/supportdesk.policy__decide_refund/qwen2.5-3b.jsonl +25 -0
  83. downshift-0.1.1/examples/supportdesk/results/supportdesk.triage__classify_category/qwen2.5-3b.jsonl +25 -0
  84. downshift-0.1.1/examples/supportdesk/results/supportdesk.triage__detect_sentiment/qwen2.5-3b.jsonl +22 -0
  85. downshift-0.1.1/examples/supportdesk/results/supportdesk.triage__tag_urgency/qwen2.5-3b.jsonl +22 -0
  86. {downshift-0.1.0.dev0 → downshift-0.1.1}/pyproject.toml +24 -0
  87. {downshift-0.1.0.dev0 → downshift-0.1.1}/src/downshift/__init__.py +1 -1
  88. {downshift-0.1.0.dev0 → downshift-0.1.1}/src/downshift/cli.py +193 -11
  89. downshift-0.1.1/src/downshift/diff.py +387 -0
  90. downshift-0.1.1/src/downshift/export.py +427 -0
  91. downshift-0.1.1/src/downshift/gitref.py +65 -0
  92. downshift-0.1.1/src/downshift/payback.py +172 -0
  93. {downshift-0.1.0.dev0 → downshift-0.1.1}/src/downshift/report.py +47 -0
  94. {downshift-0.1.0.dev0 → downshift-0.1.1}/src/downshift/scanner.py +28 -4
  95. {downshift-0.1.0.dev0 → downshift-0.1.1}/tests/cli/test_cli_help.py +1 -9
  96. downshift-0.1.1/tests/cli/test_diff_cli.py +152 -0
  97. downshift-0.1.1/tests/cli/test_e2e_pipeline.py +79 -0
  98. downshift-0.1.1/tests/cli/test_estimate_cli.py +84 -0
  99. downshift-0.1.1/tests/cli/test_export_cli.py +91 -0
  100. {downshift-0.1.0.dev0 → downshift-0.1.1}/tests/cli/test_report_cli.py +16 -0
  101. {downshift-0.1.0.dev0 → downshift-0.1.1}/tests/cli/test_run_cli.py +2 -1
  102. {downshift-0.1.0.dev0 → downshift-0.1.1}/tests/snapshots/supportdesk_report.md +40 -19
  103. {downshift-0.1.0.dev0 → downshift-0.1.1}/tests/unit/test_cost.py +5 -1
  104. {downshift-0.1.0.dev0 → downshift-0.1.1}/tests/unit/test_decide.py +20 -4
  105. downshift-0.1.1/tests/unit/test_diff.py +222 -0
  106. downshift-0.1.1/tests/unit/test_export.py +228 -0
  107. downshift-0.1.1/tests/unit/test_payback.py +363 -0
  108. {downshift-0.1.0.dev0 → downshift-0.1.1}/tests/unit/test_report.py +146 -0
  109. downshift-0.1.1/tests/unit/test_scanner_sdk_detection.py +104 -0
  110. downshift-0.1.1/web/.gitignore +5 -0
  111. downshift-0.1.1/web/SPEC.md +796 -0
  112. downshift-0.1.1/web/app/audit/page.tsx +117 -0
  113. downshift-0.1.1/web/app/callsites/[slug]/CallSiteDetail.tsx +229 -0
  114. downshift-0.1.1/web/app/callsites/[slug]/page.tsx +163 -0
  115. downshift-0.1.1/web/app/callsites/page.tsx +82 -0
  116. downshift-0.1.1/web/app/case-study/page.tsx +158 -0
  117. downshift-0.1.1/web/app/docs/page.tsx +131 -0
  118. downshift-0.1.1/web/app/globals.css +87 -0
  119. downshift-0.1.1/web/app/how-it-works/page.tsx +94 -0
  120. downshift-0.1.1/web/app/layout.tsx +37 -0
  121. downshift-0.1.1/web/app/not-found.tsx +14 -0
  122. downshift-0.1.1/web/app/page.tsx +235 -0
  123. downshift-0.1.1/web/components/Badge.tsx +47 -0
  124. downshift-0.1.1/web/components/Button.tsx +31 -0
  125. downshift-0.1.1/web/components/CodeBlock.tsx +17 -0
  126. downshift-0.1.1/web/components/Disclaimer.tsx +10 -0
  127. downshift-0.1.1/web/components/Footer.tsx +33 -0
  128. downshift-0.1.1/web/components/Nav.tsx +68 -0
  129. downshift-0.1.1/web/components/Panel.tsx +41 -0
  130. downshift-0.1.1/web/components/StatCard.tsx +45 -0
  131. downshift-0.1.1/web/components/Table.tsx +32 -0
  132. downshift-0.1.1/web/components/ThemeToggle.tsx +50 -0
  133. downshift-0.1.1/web/eslint.config.mjs +13 -0
  134. downshift-0.1.1/web/lib/__tests__/content.test.ts +37 -0
  135. downshift-0.1.1/web/lib/__tests__/data.test.ts +51 -0
  136. downshift-0.1.1/web/lib/__tests__/format.test.ts +86 -0
  137. downshift-0.1.1/web/lib/__tests__/select.test.ts +142 -0
  138. downshift-0.1.1/web/lib/content.ts +316 -0
  139. downshift-0.1.1/web/lib/data.ts +28 -0
  140. downshift-0.1.1/web/lib/format.ts +72 -0
  141. downshift-0.1.1/web/lib/formatPayback.test.ts +10 -0
  142. downshift-0.1.1/web/lib/select.ts +53 -0
  143. downshift-0.1.1/web/lib/types.ts +145 -0
  144. downshift-0.1.1/web/next-env.d.ts +6 -0
  145. downshift-0.1.1/web/next.config.ts +11 -0
  146. downshift-0.1.1/web/package-lock.json +7213 -0
  147. downshift-0.1.1/web/package.json +33 -0
  148. downshift-0.1.1/web/postcss.config.mjs +2 -0
  149. downshift-0.1.1/web/public/data/audit.json +205 -0
  150. downshift-0.1.1/web/public/data/callsites.json +1067 -0
  151. downshift-0.1.1/web/public/data/evals_summary.json +2824 -0
  152. downshift-0.1.1/web/public/data/report.md +146 -0
  153. downshift-0.1.1/web/public/data/summary.json +80 -0
  154. downshift-0.1.1/web/tsconfig.json +21 -0
  155. downshift-0.1.1/web/vitest.config.ts +12 -0
  156. downshift-0.1.0.dev0/.github/workflows/ci.yml +0 -30
  157. downshift-0.1.0.dev0/CHANGELOG.md +0 -19
  158. downshift-0.1.0.dev0/PKG-INFO +0 -24
  159. downshift-0.1.0.dev0/README.md +0 -1
  160. {downshift-0.1.0.dev0 → downshift-0.1.1}/.bob/custom_modes.yaml +0 -0
  161. {downshift-0.1.0.dev0 → downshift-0.1.1}/.bob/rules-agent/AGENTS.md +0 -0
  162. {downshift-0.1.0.dev0 → downshift-0.1.1}/.bob/rules-ask/AGENTS.md +0 -0
  163. {downshift-0.1.0.dev0 → downshift-0.1.1}/.bob/rules-plan/AGENTS.md +0 -0
  164. {downshift-0.1.0.dev0 → downshift-0.1.1}/.bob/skills/downshift-audit/SKILL.md +0 -0
  165. {downshift-0.1.0.dev0 → downshift-0.1.1}/.bob/skills/downshift-audit/schema-reference.md +0 -0
  166. {downshift-0.1.0.dev0 → downshift-0.1.1}/.bob/skills/downshift-evals/SKILL.md +0 -0
  167. {downshift-0.1.0.dev0 → downshift-0.1.1}/.bob/skills/downshift-evals/format-reference.md +0 -0
  168. {downshift-0.1.0.dev0 → downshift-0.1.1}/.bobignore +0 -0
  169. {downshift-0.1.0.dev0 → downshift-0.1.1}/.gitignore +0 -0
  170. {downshift-0.1.0.dev0 → downshift-0.1.1}/.pre-commit-config.yaml +0 -0
  171. {downshift-0.1.0.dev0 → downshift-0.1.1}/AGENTS.md +0 -0
  172. {downshift-0.1.0.dev0 → downshift-0.1.1}/CONTRIBUTING.md +0 -0
  173. {downshift-0.1.0.dev0 → downshift-0.1.1}/LICENSE +0 -0
  174. {downshift-0.1.0.dev0 → downshift-0.1.1}/bob_sessions/.gitkeep +0 -0
  175. {downshift-0.1.0.dev0 → downshift-0.1.1}/bob_sessions/downshift_task01_init.png +0 -0
  176. {downshift-0.1.0.dev0 → downshift-0.1.1}/bob_sessions/downshift_task02_architecture.png +0 -0
  177. {downshift-0.1.0.dev0 → downshift-0.1.1}/bob_sessions/downshift_task03_audit_a.png +0 -0
  178. {downshift-0.1.0.dev0 → downshift-0.1.1}/bob_sessions/downshift_task03_audit_b.png +0 -0
  179. {downshift-0.1.0.dev0 → downshift-0.1.1}/bob_sessions/downshift_task04_evals_a.png +0 -0
  180. {downshift-0.1.0.dev0 → downshift-0.1.1}/bob_sessions/downshift_task04_evals_b.png +0 -0
  181. {downshift-0.1.0.dev0 → downshift-0.1.1}/bob_sessions/downshift_task05_report_a01.png +0 -0
  182. {downshift-0.1.0.dev0 → downshift-0.1.1}/bob_sessions/downshift_task05_report_a02.png +0 -0
  183. {downshift-0.1.0.dev0 → downshift-0.1.1}/bob_sessions/downshift_task05_report_a03.png +0 -0
  184. {downshift-0.1.0.dev0 → downshift-0.1.1}/bob_sessions/downshift_task05_report_b01.png +0 -0
  185. {downshift-0.1.0.dev0 → downshift-0.1.1}/bob_sessions/downshift_task05_report_b02.png +0 -0
  186. {downshift-0.1.0.dev0 → downshift-0.1.1}/bob_sessions/downshift_task05_report_b03.png +0 -0
  187. {downshift-0.1.0.dev0 → downshift-0.1.1}/bob_sessions/downshift_task05_report_b04.png +0 -0
  188. {downshift-0.1.0.dev0 → downshift-0.1.1}/bob_sessions/downshift_task05_report_b05.png +0 -0
  189. {downshift-0.1.0.dev0 → downshift-0.1.1}/bob_sessions/downshift_task06_apply_a.png +0 -0
  190. {downshift-0.1.0.dev0 → downshift-0.1.1}/bob_sessions/downshift_task06_apply_b.png +0 -0
  191. {downshift-0.1.0.dev0 → downshift-0.1.1}/bob_sessions/downshift_task06_apply_c.png +0 -0
  192. {downshift-0.1.0.dev0 → downshift-0.1.1}/docs/architecture.md +0 -0
  193. {downshift-0.1.0.dev0 → downshift-0.1.1}/docs/bob.md +0 -0
  194. {downshift-0.1.0.dev0 → downshift-0.1.1}/docs/plans/report-plan.md +0 -0
  195. {downshift-0.1.0.dev0 → downshift-0.1.1}/docs/specs/report.md +0 -0
  196. {downshift-0.1.0.dev0 → downshift-0.1.1}/downshift.example.yaml +0 -0
  197. {downshift-0.1.0.dev0 → downshift-0.1.1}/examples/supportdesk/data/refund_policy.md +0 -0
  198. {downshift-0.1.0.dev0 → downshift-0.1.1}/examples/supportdesk/data/tickets.jsonl +0 -0
  199. {downshift-0.1.0.dev0 → downshift-0.1.1}/examples/supportdesk/downshift.audit.json +0 -0
  200. {downshift-0.1.0.dev0 → downshift-0.1.1}/examples/supportdesk/downshift.scan.after.json +0 -0
  201. {downshift-0.1.0.dev0 → downshift-0.1.1}/examples/supportdesk/downshift.scan.json +0 -0
  202. {downshift-0.1.0.dev0 → downshift-0.1.1}/examples/supportdesk/evals/supportdesk.agent_assist__draft_reply.jsonl +0 -0
  203. {downshift-0.1.0.dev0 → downshift-0.1.1}/examples/supportdesk/evals/supportdesk.agent_assist__summarize_for_agent.jsonl +0 -0
  204. {downshift-0.1.0.dev0 → downshift-0.1.1}/examples/supportdesk/evals/supportdesk.extract__extract_order_info.jsonl +0 -0
  205. {downshift-0.1.0.dev0 → downshift-0.1.1}/examples/supportdesk/evals/supportdesk.misc_utils__lang_of.jsonl +0 -0
  206. {downshift-0.1.0.dev0 → downshift-0.1.1}/examples/supportdesk/evals/supportdesk.policy__decide_refund.jsonl +0 -0
  207. {downshift-0.1.0.dev0 → downshift-0.1.1}/examples/supportdesk/evals/supportdesk.triage__classify_category.jsonl +0 -0
  208. {downshift-0.1.0.dev0 → downshift-0.1.1}/examples/supportdesk/evals/supportdesk.triage__detect_sentiment.jsonl +0 -0
  209. {downshift-0.1.0.dev0 → downshift-0.1.1}/examples/supportdesk/evals/supportdesk.triage__tag_urgency.jsonl +0 -0
  210. {downshift-0.1.0.dev0 → downshift-0.1.1}/examples/supportdesk/results/supportdesk.agent_assist__draft_reply/qwen2.5-0.5b.jsonl +0 -0
  211. {downshift-0.1.0.dev0 → downshift-0.1.1}/examples/supportdesk/results/supportdesk.agent_assist__draft_reply/qwen2.5-1.5b.jsonl +0 -0
  212. {downshift-0.1.0.dev0 → downshift-0.1.1}/examples/supportdesk/results/supportdesk.agent_assist__draft_reply/qwen2.5-7b.jsonl +0 -0
  213. {downshift-0.1.0.dev0 → downshift-0.1.1}/examples/supportdesk/results/supportdesk.agent_assist__summarize_for_agent/qwen2.5-0.5b.jsonl +0 -0
  214. {downshift-0.1.0.dev0 → downshift-0.1.1}/examples/supportdesk/results/supportdesk.agent_assist__summarize_for_agent/qwen2.5-1.5b.jsonl +0 -0
  215. {downshift-0.1.0.dev0 → downshift-0.1.1}/examples/supportdesk/results/supportdesk.agent_assist__summarize_for_agent/qwen2.5-7b.jsonl +0 -0
  216. {downshift-0.1.0.dev0 → downshift-0.1.1}/examples/supportdesk/results/supportdesk.extract__extract_order_info/qwen2.5-0.5b.jsonl +0 -0
  217. {downshift-0.1.0.dev0 → downshift-0.1.1}/examples/supportdesk/results/supportdesk.extract__extract_order_info/qwen2.5-1.5b.jsonl +0 -0
  218. {downshift-0.1.0.dev0 → downshift-0.1.1}/examples/supportdesk/results/supportdesk.extract__extract_order_info/qwen2.5-7b.jsonl +0 -0
  219. {downshift-0.1.0.dev0 → downshift-0.1.1}/examples/supportdesk/results/supportdesk.misc_utils__lang_of/qwen2.5-0.5b.jsonl +0 -0
  220. {downshift-0.1.0.dev0 → downshift-0.1.1}/examples/supportdesk/results/supportdesk.misc_utils__lang_of/qwen2.5-1.5b.jsonl +0 -0
  221. {downshift-0.1.0.dev0 → downshift-0.1.1}/examples/supportdesk/results/supportdesk.misc_utils__lang_of/qwen2.5-7b.jsonl +0 -0
  222. {downshift-0.1.0.dev0 → downshift-0.1.1}/examples/supportdesk/results/supportdesk.policy__decide_refund/qwen2.5-0.5b.jsonl +0 -0
  223. {downshift-0.1.0.dev0 → downshift-0.1.1}/examples/supportdesk/results/supportdesk.policy__decide_refund/qwen2.5-1.5b.jsonl +0 -0
  224. {downshift-0.1.0.dev0 → downshift-0.1.1}/examples/supportdesk/results/supportdesk.policy__decide_refund/qwen2.5-7b.jsonl +0 -0
  225. {downshift-0.1.0.dev0 → downshift-0.1.1}/examples/supportdesk/results/supportdesk.triage__classify_category/qwen2.5-0.5b.jsonl +0 -0
  226. {downshift-0.1.0.dev0 → downshift-0.1.1}/examples/supportdesk/results/supportdesk.triage__classify_category/qwen2.5-1.5b.jsonl +0 -0
  227. {downshift-0.1.0.dev0 → downshift-0.1.1}/examples/supportdesk/results/supportdesk.triage__classify_category/qwen2.5-7b.jsonl +0 -0
  228. {downshift-0.1.0.dev0 → downshift-0.1.1}/examples/supportdesk/results/supportdesk.triage__detect_sentiment/qwen2.5-0.5b.jsonl +0 -0
  229. {downshift-0.1.0.dev0 → downshift-0.1.1}/examples/supportdesk/results/supportdesk.triage__detect_sentiment/qwen2.5-1.5b.jsonl +0 -0
  230. {downshift-0.1.0.dev0 → downshift-0.1.1}/examples/supportdesk/results/supportdesk.triage__detect_sentiment/qwen2.5-7b.jsonl +0 -0
  231. {downshift-0.1.0.dev0 → downshift-0.1.1}/examples/supportdesk/results/supportdesk.triage__tag_urgency/qwen2.5-0.5b.jsonl +0 -0
  232. {downshift-0.1.0.dev0 → downshift-0.1.1}/examples/supportdesk/results/supportdesk.triage__tag_urgency/qwen2.5-1.5b.jsonl +0 -0
  233. {downshift-0.1.0.dev0 → downshift-0.1.1}/examples/supportdesk/results/supportdesk.triage__tag_urgency/qwen2.5-7b.jsonl +0 -0
  234. {downshift-0.1.0.dev0 → downshift-0.1.1}/examples/supportdesk/supportdesk/__init__.py +0 -0
  235. {downshift-0.1.0.dev0 → downshift-0.1.1}/examples/supportdesk/supportdesk/__main__.py +0 -0
  236. {downshift-0.1.0.dev0 → downshift-0.1.1}/examples/supportdesk/supportdesk/agent_assist.py +0 -0
  237. {downshift-0.1.0.dev0 → downshift-0.1.1}/examples/supportdesk/supportdesk/extract.py +0 -0
  238. {downshift-0.1.0.dev0 → downshift-0.1.1}/examples/supportdesk/supportdesk/llm.py +0 -0
  239. {downshift-0.1.0.dev0 → downshift-0.1.1}/examples/supportdesk/supportdesk/misc_utils.py +0 -0
  240. {downshift-0.1.0.dev0 → downshift-0.1.1}/examples/supportdesk/supportdesk/models.py +0 -0
  241. {downshift-0.1.0.dev0 → downshift-0.1.1}/examples/supportdesk/supportdesk/pipeline.py +0 -0
  242. {downshift-0.1.0.dev0 → downshift-0.1.1}/examples/supportdesk/supportdesk/policy.py +0 -0
  243. {downshift-0.1.0.dev0 → downshift-0.1.1}/examples/supportdesk/supportdesk/triage.py +0 -0
  244. {downshift-0.1.0.dev0 → downshift-0.1.1}/src/downshift/audit.py +0 -0
  245. {downshift-0.1.0.dev0 → downshift-0.1.1}/src/downshift/config.py +0 -0
  246. {downshift-0.1.0.dev0 → downshift-0.1.1}/src/downshift/cost.py +0 -0
  247. {downshift-0.1.0.dev0 → downshift-0.1.1}/src/downshift/decide.py +0 -0
  248. {downshift-0.1.0.dev0 → downshift-0.1.1}/src/downshift/evalgen.py +0 -0
  249. {downshift-0.1.0.dev0 → downshift-0.1.1}/src/downshift/evals.py +0 -0
  250. {downshift-0.1.0.dev0 → downshift-0.1.1}/src/downshift/llm.py +0 -0
  251. {downshift-0.1.0.dev0 → downshift-0.1.1}/src/downshift/py.typed +0 -0
  252. {downshift-0.1.0.dev0 → downshift-0.1.1}/src/downshift/resolve.py +0 -0
  253. {downshift-0.1.0.dev0 → downshift-0.1.1}/src/downshift/runner.py +0 -0
  254. {downshift-0.1.0.dev0 → downshift-0.1.1}/src/downshift/schema.py +0 -0
  255. {downshift-0.1.0.dev0 → downshift-0.1.1}/src/downshift/scorer.py +0 -0
  256. {downshift-0.1.0.dev0 → downshift-0.1.1}/tests/cli/test_audit_commands.py +0 -0
  257. {downshift-0.1.0.dev0 → downshift-0.1.1}/tests/cli/test_check_evals.py +0 -0
  258. {downshift-0.1.0.dev0 → downshift-0.1.1}/tests/cli/test_cli_scan.py +0 -0
  259. {downshift-0.1.0.dev0 → downshift-0.1.1}/tests/cli/test_evalgen_cli.py +0 -0
  260. {downshift-0.1.0.dev0 → downshift-0.1.1}/tests/cli/test_rescore_cli.py +0 -0
  261. {downshift-0.1.0.dev0 → downshift-0.1.1}/tests/fixtures/supportdesk_v0/README.md +0 -0
  262. {downshift-0.1.0.dev0 → downshift-0.1.1}/tests/fixtures/supportdesk_v0/supportdesk/__init__.py +0 -0
  263. {downshift-0.1.0.dev0 → downshift-0.1.1}/tests/fixtures/supportdesk_v0/supportdesk/__main__.py +0 -0
  264. {downshift-0.1.0.dev0 → downshift-0.1.1}/tests/fixtures/supportdesk_v0/supportdesk/agent_assist.py +0 -0
  265. {downshift-0.1.0.dev0 → downshift-0.1.1}/tests/fixtures/supportdesk_v0/supportdesk/extract.py +0 -0
  266. {downshift-0.1.0.dev0 → downshift-0.1.1}/tests/fixtures/supportdesk_v0/supportdesk/llm.py +0 -0
  267. {downshift-0.1.0.dev0 → downshift-0.1.1}/tests/fixtures/supportdesk_v0/supportdesk/misc_utils.py +0 -0
  268. {downshift-0.1.0.dev0 → downshift-0.1.1}/tests/fixtures/supportdesk_v0/supportdesk/pipeline.py +0 -0
  269. {downshift-0.1.0.dev0 → downshift-0.1.1}/tests/fixtures/supportdesk_v0/supportdesk/policy.py +0 -0
  270. {downshift-0.1.0.dev0 → downshift-0.1.1}/tests/fixtures/supportdesk_v0/supportdesk/triage.py +0 -0
  271. {downshift-0.1.0.dev0 → downshift-0.1.1}/tests/integration/test_evalgen_ollama.py +0 -0
  272. {downshift-0.1.0.dev0 → downshift-0.1.1}/tests/integration/test_ollama.py +0 -0
  273. {downshift-0.1.0.dev0 → downshift-0.1.1}/tests/integration/test_run_ollama.py +0 -0
  274. {downshift-0.1.0.dev0 → downshift-0.1.1}/tests/unit/test_audit.py +0 -0
  275. {downshift-0.1.0.dev0 → downshift-0.1.1}/tests/unit/test_config.py +0 -0
  276. {downshift-0.1.0.dev0 → downshift-0.1.1}/tests/unit/test_evalgen.py +0 -0
  277. {downshift-0.1.0.dev0 → downshift-0.1.1}/tests/unit/test_evals.py +0 -0
  278. {downshift-0.1.0.dev0 → downshift-0.1.1}/tests/unit/test_llm.py +0 -0
  279. {downshift-0.1.0.dev0 → downshift-0.1.1}/tests/unit/test_report_snapshot.py +0 -0
  280. {downshift-0.1.0.dev0 → downshift-0.1.1}/tests/unit/test_rescore.py +0 -0
  281. {downshift-0.1.0.dev0 → downshift-0.1.1}/tests/unit/test_resolve.py +0 -0
  282. {downshift-0.1.0.dev0 → downshift-0.1.1}/tests/unit/test_runner.py +0 -0
  283. {downshift-0.1.0.dev0 → downshift-0.1.1}/tests/unit/test_scanner.py +0 -0
  284. {downshift-0.1.0.dev0 → downshift-0.1.1}/tests/unit/test_schema.py +0 -0
  285. {downshift-0.1.0.dev0 → downshift-0.1.1}/tests/unit/test_scorer.py +0 -0
@@ -0,0 +1,53 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+ strategy:
12
+ fail-fast: false
13
+ matrix:
14
+ python-version: ["3.10", "3.11", "3.12"]
15
+ steps:
16
+ - uses: actions/checkout@v5
17
+ - uses: actions/setup-python@v6
18
+ with:
19
+ python-version: ${{ matrix.python-version }}
20
+ cache: pip
21
+ - name: Install
22
+ run: pip install -e ".[dev]"
23
+ - name: Lint
24
+ run: |
25
+ ruff check .
26
+ ruff format --check .
27
+ - name: Type check
28
+ run: mypy src
29
+ - name: Test
30
+ run: pytest --cov=downshift --cov-report=term-missing --cov-fail-under=80
31
+
32
+ web:
33
+ runs-on: ubuntu-latest
34
+ defaults:
35
+ run:
36
+ working-directory: web
37
+ steps:
38
+ - uses: actions/checkout@v5
39
+ - uses: actions/setup-node@v7
40
+ with:
41
+ node-version: "22"
42
+ cache: npm
43
+ cache-dependency-path: web/package-lock.json
44
+ - name: Install
45
+ run: npm ci
46
+ - name: Lint
47
+ run: npm run lint
48
+ - name: Type check
49
+ run: npm run typecheck
50
+ - name: Test
51
+ run: npm test
52
+ - name: Build
53
+ run: npm run build
@@ -0,0 +1,20 @@
1
+ name: Cost diff
2
+
3
+ on:
4
+ pull_request:
5
+
6
+ permissions:
7
+ contents: read
8
+ pull-requests: write
9
+ issues: write
10
+
11
+ jobs:
12
+ cost-diff:
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - uses: actions/checkout@v5
16
+ - name: Downshift cost diff (SupportDesk)
17
+ uses: ./
18
+ with:
19
+ path: examples/supportdesk
20
+ package: .
@@ -17,7 +17,7 @@ jobs:
17
17
  pip install build twine
18
18
  python -m build
19
19
  twine check dist/*
20
- - uses: actions/upload-artifact@v4
20
+ - uses: actions/upload-artifact@v7
21
21
  with:
22
22
  name: dist
23
23
  path: dist/
@@ -29,7 +29,7 @@ jobs:
29
29
  permissions:
30
30
  id-token: write
31
31
  steps:
32
- - uses: actions/download-artifact@v4
32
+ - uses: actions/download-artifact@v8
33
33
  with:
34
34
  name: dist
35
35
  path: dist/
@@ -0,0 +1,60 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here.
4
+ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
5
+ and this project uses [Semantic Versioning](https://semver.org/).
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [0.1.1] - 2026-09-26
10
+
11
+ ### Added
12
+ - `downshift report` shows the one-time analysis cost (eval model calls plus estimated judge
13
+ calls, at config prices) and the payback time. New `--audit-cost USD` option for an
14
+ assistant audit. `downshift export` adds the same numbers to `summary.json`.
15
+ SupportDesk: $0.65 one-time, pays back in 51 minutes.
16
+ - Web Overview shows the analysis cost and payback time.
17
+ - mem0 case study: 51 eval cases for 3 features and a local reranker run (no safe downgrade;
18
+ best candidates keep 76% of baseline quality).
19
+
20
+ ### Fixed
21
+ - The judge estimate note only appears when there are judge calls.
22
+ - Clear "No payback" sentence when nothing was downgraded; "1 minute" is singular.
23
+
24
+ ## [0.1.0] - 2026-09-26
25
+
26
+ First public release.
27
+
28
+ ### Added
29
+ - `downshift scan`: AST scanner for OpenAI (v1 SDK) chat/responses and Anthropic messages calls. Resolves models through constants, imports, env var defaults, dict lookups and parameter defaults; recovers prompt templates; records callers.
30
+ - `callsites.json` schema with strict validation, shared by the scanner and Bob.
31
+ - `downshift validate` and `downshift compare`: check a Bob audit and compare it with the ast scan.
32
+ - IBM Bob integration in `.bob/`: Downshift Auditor and Downshift Eval Writer custom modes with the `downshift-audit` and `downshift-evals` skills.
33
+ - Eval sets (`evals/<call_site>.jsonl`), `downshift check-evals`, and `downshift evalgen` to write evals with any configured model.
34
+ - `downshift run`: runs each call site's evals on the baseline and candidate models. Resumable, with warm-up and token and latency tracking.
35
+ - Scoring by exact match, JSON fields, or an LLM judge, plus `downshift rescore` with a separate hosted judge.
36
+ - Decisions (cheapest model that keeps the quality threshold and minimum pass rate), monthly cost projections, and `downshift report`.
37
+ - `downshift diff` and a composite GitHub Action (`action.yml`) that comments the projected monthly cost change on every PR, with an optional `fail-above` limit.
38
+ - `downshift estimate`: price a scan or audit file, or compare two, without running evals.
39
+ - `downshift export` and a demo web app (Next.js, deployed at https://downshift-llm.vercel.app/).
40
+ - `examples/supportdesk`: demo app with 8 LLM features, 40 synthetic tickets and a refund policy document.
41
+ - Case studies on OrchestrAI (MIT) and mem0 (Apache-2.0).
42
+ - End-to-end pipeline smoke test; CI fails below 80% coverage.
43
+
44
+ ### Fixed
45
+ - Scanner reads files with a UTF-8 BOM.
46
+ - Scanner detects SDK calls that pass the model through `**kwargs` when the SDK is imported.
47
+
48
+ ### Notes
49
+ - `diff` and `estimate` are static projections for per-PR deltas, not absolute spend.
50
+ - The GitHub Action now installs `downshift>=0.1.0` by default.
51
+
52
+ ## [0.1.0.dev0] - 2026-09-26
53
+
54
+ ### Added
55
+ - Pre-release to claim the PyPI name and test Trusted Publishing.
56
+
57
+ [Unreleased]: https://github.com/ana-lan/downshift/compare/v0.1.1...HEAD
58
+ [0.1.1]: https://github.com/ana-lan/downshift/compare/v0.1.0...v0.1.1
59
+ [0.1.0]: https://github.com/ana-lan/downshift/compare/v0.1.0.dev0...v0.1.0
60
+ [0.1.0.dev0]: https://github.com/ana-lan/downshift/releases/tag/v0.1.0.dev0
@@ -0,0 +1,224 @@
1
+ Metadata-Version: 2.5
2
+ Name: downshift
3
+ Version: 0.1.1
4
+ Summary: Cut LLM costs per PR: find every LLM call, prove which can use cheaper models, and show the cost impact of every change.
5
+ Project-URL: Homepage, https://github.com/ana-lan/downshift
6
+ Project-URL: Demo, https://downshift-llm.vercel.app/
7
+ Project-URL: Documentation, https://github.com/ana-lan/downshift/tree/main/docs
8
+ Project-URL: Issues, https://github.com/ana-lan/downshift/issues
9
+ Project-URL: Changelog, https://github.com/ana-lan/downshift/blob/main/CHANGELOG.md
10
+ Author: Anagha
11
+ License: MIT
12
+ License-File: LICENSE
13
+ Keywords: ci,cost,evals,github-action,llm,model-routing,openai,static-analysis
14
+ Classifier: Development Status :: 4 - Beta
15
+ Classifier: Environment :: Console
16
+ Classifier: Intended Audience :: Developers
17
+ Classifier: License :: OSI Approved :: MIT License
18
+ Classifier: Operating System :: OS Independent
19
+ Classifier: Programming Language :: Python :: 3
20
+ Classifier: Programming Language :: Python :: 3 :: Only
21
+ Classifier: Programming Language :: Python :: 3.10
22
+ Classifier: Programming Language :: Python :: 3.11
23
+ Classifier: Programming Language :: Python :: 3.12
24
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
25
+ Classifier: Topic :: Software Development :: Quality Assurance
26
+ Classifier: Topic :: Software Development :: Testing
27
+ Requires-Python: >=3.10
28
+ Requires-Dist: openai>=1.40
29
+ Requires-Dist: pyyaml>=6.0
30
+ Requires-Dist: rich>=13.0
31
+ Requires-Dist: tiktoken>=0.7
32
+ Requires-Dist: typer>=0.12
33
+ Provides-Extra: dev
34
+ Requires-Dist: build>=1.2; extra == 'dev'
35
+ Requires-Dist: mypy>=1.10; extra == 'dev'
36
+ Requires-Dist: pre-commit>=3.7; extra == 'dev'
37
+ Requires-Dist: pytest-cov>=5.0; extra == 'dev'
38
+ Requires-Dist: pytest>=8.0; extra == 'dev'
39
+ Requires-Dist: ruff>=0.6; extra == 'dev'
40
+ Requires-Dist: twine>=5.0; extra == 'dev'
41
+ Requires-Dist: types-pyyaml; extra == 'dev'
42
+ Description-Content-Type: text/markdown
43
+
44
+ <div align="center">
45
+
46
+ <h1>Downshift</h1>
47
+
48
+ <p><strong>Cut LLM costs per PR.</strong> Downshift finds every LLM call in a Python repo, tests cheaper models against evals written for each call site, recommends the safe downgrades, and shows the projected cost impact of every pull request.</p>
49
+
50
+ <p>
51
+ <a href="https://pypi.org/project/downshift/"><img src="https://img.shields.io/pypi/v/downshift" alt="PyPI"></a>
52
+ <a href="https://pypi.org/project/downshift/"><img src="https://img.shields.io/pypi/pyversions/downshift" alt="Python"></a>
53
+ <a href="https://github.com/ana-lan/downshift/actions/workflows/ci.yml"><img src="https://github.com/ana-lan/downshift/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
54
+ <a href="https://github.com/ana-lan/downshift/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue" alt="License: MIT"></a>
55
+ </p>
56
+
57
+ <p><strong>Live demo:</strong> <a href="https://downshift-llm.vercel.app/">downshift-llm.vercel.app</a></p>
58
+
59
+ </div>
60
+
61
+ Downshift is the toolkit, [IBM Bob](https://bob.ibm.com/) is the brain. Static analysis gets most of the way. Bob closes the gaps that need real code understanding (models and prompts only known at runtime, one helper serving several features), writes the evals, applies the changes, and reviews PRs. Everything except the audit runs without Bob.
62
+
63
+ ## Quickstart
64
+
65
+ ```bash
66
+ pip install downshift
67
+ curl -O https://raw.githubusercontent.com/ana-lan/downshift/main/downshift.example.yaml
68
+ downshift scan path/to/repo
69
+ downshift estimate path/to/repo/.downshift/callsites.json -c downshift.example.yaml
70
+ ```
71
+
72
+ `scan` lists every LLM call site with its model, prompt and output type. `estimate` prices them per month with the prices and volumes in the config. Edit both to match your provider and traffic.
73
+
74
+ ## Full pipeline
75
+
76
+ ```bash
77
+ downshift scan path/to/repo # 1. find call sites
78
+ downshift evalgen --callsites path/to/repo/.downshift/callsites.json \
79
+ --out path/to/repo/.downshift/evals # 2. write evals (or use Bob)
80
+ downshift run --callsites path/to/repo/.downshift/callsites.json # 3. baseline vs cheaper models
81
+ downshift report --callsites path/to/repo/.downshift/callsites.json \
82
+ --out report.md # 4. decision per call site
83
+ downshift diff path/to/repo --base main # 5. cost change of your branch
84
+ ```
85
+
86
+ | Command | What it does |
87
+ |---|---|
88
+ | `scan` | Find every LLM call site in a repository. |
89
+ | `validate` | Check a callsites or audit file against the schema. |
90
+ | `compare` | Compare the ast scan with a Bob audit, side by side. |
91
+ | `check-evals` | Check eval files against the call sites they test. |
92
+ | `evalgen` | Generate an eval set for each call site with a configured model. |
93
+ | `run` | Run each call site's evals on the baseline and candidate models, and score them. |
94
+ | `rescore` | Re-grade saved outputs of judge-graded call sites with the configured judge. |
95
+ | `report` | Render the cost and quality report. |
96
+ | `estimate` | Project monthly LLM cost from a scan or audit file. No evals needed. |
97
+ | `diff` | Show the projected LLM cost change between two git refs. |
98
+ | `export` | Export JSON and the report for the demo web app. |
99
+
100
+ ## How it works
101
+
102
+ ```
103
+ repo ──► scan (ast) ──► Bob audit ──► evals ──► run ──► decide ──► report
104
+ static runtime per base + cheapest cost +
105
+ analysis models, call cheaper model that quality
106
+ shared site models keeps the per site
107
+ helpers quality bar
108
+ PR ──► diff (base vs head) ──► GitHub Action comment: "+$X/month"
109
+ ```
110
+
111
+ 1. **Scan.** An AST scanner finds OpenAI (v1 SDK) and Anthropic calls. It resolves models through constants, imports, env var defaults, dict lookups and parameter defaults, recovers prompt templates, and records callers.
112
+ 2. **Audit (Bob).** The Downshift Auditor mode reads only the call sites the scanner could not resolve, splits shared helpers into the features they serve, and adds purpose, output contract, difficulty and grading. Output is checked with `downshift validate`.
113
+ 3. **Evals.** 20 to 25 cases per call site, graded by exact match, JSON fields, or an LLM judge.
114
+ 4. **Run and decide.** Each call site runs on the baseline and every cheaper candidate. A candidate wins if it keeps `quality_threshold` of the baseline's quality and passes `min_pass_rate` of cases. The cheapest winner is recommended.
115
+ 5. **Report and diff.** Monthly cost before and after, per call site, from real token counts, your prices and your volumes. `diff` projects the change a branch makes, and the GitHub Action posts it on every PR.
116
+
117
+ ## Results on the demo app
118
+
119
+ SupportDesk (`examples/supportdesk`) is a synthetic support ticket app with 8 LLM features, 40 synthetic tickets and a refund policy document.
120
+
121
+ | | ast scan | Bob audit |
122
+ |---|---|---|
123
+ | Call sites / features | 7 | 8 |
124
+ | Models resolved | 6 | 8 |
125
+ | Prompts resolved | 5 | 8 |
126
+
127
+ After evals on local Qwen 2.5 models (7b baseline; 3b, 1.5b, 0.5b candidates), **3 of 8 call sites can downgrade**. Projected monthly cost goes from **$3,137.26 to $2,572.61, saving $564.65 (18%)**, while the mean pass rate moves from 68.3% to 68.9%. Full report: [`examples/supportdesk/downshift.report.md`](https://github.com/ana-lan/downshift/blob/main/examples/supportdesk/downshift.report.md). Bob then applied the decisions and opened the PR itself.
128
+
129
+ ## Case studies on real repos
130
+
131
+ | Repo | License | ast scan | Bob audit | Projected monthly cost |
132
+ |---|---|---|---|---|
133
+ | [OrchestrAI](https://github.com/ana-lan/downshift/blob/main/docs/case-study.md) | MIT | 1 site, 0 models | 5 features, 5/5 models, 5/5 prompts | $540 static-only vs $2,409 with Bob (about 4.5x) |
134
+ | [mem0](https://github.com/ana-lan/downshift/blob/main/docs/case-study-mem0.md) | Apache-2.0 | 14 sites, 1 model | 4 real features on the default path, all resolved | $307 static-only vs $2,330 with Bob (about 7.6x) |
135
+
136
+ In both repos, one shared helper hid most of the spend from static analysis. In mem0, Bob found that the reranker makes one LLM call per candidate document, about 29% of the projected bill. Both audits cost under 4 Bobcoins. See them on the web: https://downshift-llm.vercel.app/case-study/
137
+
138
+ ## GitHub Action
139
+
140
+ Add a projected cost comment to every pull request:
141
+
142
+ ```yaml
143
+ name: LLM cost diff
144
+ on: pull_request
145
+
146
+ permissions:
147
+ contents: read
148
+ pull-requests: write
149
+ issues: write
150
+
151
+ jobs:
152
+ cost-diff:
153
+ runs-on: ubuntu-latest
154
+ steps:
155
+ - uses: actions/checkout@v5
156
+ - uses: ana-lan/downshift@v0.1.1
157
+ with:
158
+ path: . # directory to scan
159
+ fail-above: "500" # optional: fail if the monthly increase is above $500
160
+ ```
161
+
162
+ Inputs: `path`, `base` (default: the PR base branch), `config` (default: `downshift.yaml` in `path`), `fail-above`, `comment`, `package`, `python-version`, `github-token`.
163
+
164
+ ![Downshift comment on a PR that adds an expensive call](https://raw.githubusercontent.com/ana-lan/downshift/main/docs/images/bad_pr_cost_diff_a.png)
165
+
166
+ ## Configuration
167
+
168
+ `downshift.yaml` sits next to the code you scan. Start from [`downshift.example.yaml`](https://github.com/ana-lan/downshift/blob/main/downshift.example.yaml).
169
+
170
+ ```yaml
171
+ version: 1
172
+ provider:
173
+ base_url: "http://localhost:11434/v1" # any OpenAI-compatible endpoint
174
+ api_key_env: null # env var holding the key, or null
175
+ models:
176
+ baseline: "qwen2.5:7b"
177
+ candidates: ["qwen2.5:3b", "qwen2.5:1.5b", "qwen2.5:0.5b"]
178
+ judge: null # defaults to the baseline
179
+ quality_threshold: 0.95
180
+ min_pass_rate: 0.80
181
+ pricing: # USD per 1M tokens
182
+ "qwen2.5:7b": { input: 2.50, output: 10.00, tier: premium }
183
+ "qwen2.5:3b": { input: 0.40, output: 1.60, tier: standard }
184
+ "qwen2.5:1.5b": { input: 0.15, output: 0.60, tier: budget }
185
+ "qwen2.5:0.5b": { input: 0.10, output: 0.40, tier: nano }
186
+ volume:
187
+ default_per_day: 20000
188
+ per_call_site: {} # e.g. "app/triage.py::classify": 50000
189
+ scan:
190
+ include: ["*.py"]
191
+ exclude: ["tests/*"]
192
+ ```
193
+
194
+ ## Using it with IBM Bob
195
+
196
+ The `.bob/` folder ships two custom modes (Downshift Auditor, Downshift Eval Writer) and their skills. The auditor can only edit the audit file, never your code. Setup and prompts: [`docs/bob.md`](https://github.com/ana-lan/downshift/blob/main/docs/bob.md). Every Bob task in this project has a screenshot in [`bob_sessions/`](https://github.com/ana-lan/downshift/tree/main/bob_sessions). In total, 11 tasks used 22.12 of 40 Bobcoins.
197
+
198
+ ## Limitations
199
+
200
+ - **Dollar figures are projections**: real token counts x prices x assumed volume, all from your config. The demo uses illustrative prices; the case studies use list prices.
201
+ - `diff` and `estimate` are static. Unresolved models are priced as the baseline, and missing `max_tokens` assumes 256 output tokens. Use them for per-PR deltas, not absolute spend.
202
+ - Python only. Detects the OpenAI v1 SDK and Anthropic messages API. Pre-v1 `openai.ChatCompletion.create`, LiteLLM and LangChain are not detected yet.
203
+ - The demo runs local Qwen 2.5 models on a laptop, not a production API benchmark. Eval inputs are synthetic.
204
+
205
+ ## Related work
206
+
207
+ [burnrate](https://pypi.org/project/burnrate/) audits LLM calls with the AST and swaps models. Downshift adds per-call-site evals that prove quality holds, a per-PR cost diff in CI, and a Bob auditor for what static analysis cannot see.
208
+
209
+ ## Development
210
+
211
+ ```bash
212
+ git clone https://github.com/ana-lan/downshift && cd downshift
213
+ python -m venv .venv && source .venv/bin/activate
214
+ pip install -e ".[dev]"
215
+ ruff format . && ruff check . && mypy src
216
+ python -m pytest # 577 tests, no network, no models (97% coverage)
217
+ python -m pytest -m integration # needs a local Ollama
218
+ ```
219
+
220
+ The web demo lives in `web/` (Next.js, static export). See [CONTRIBUTING.md](https://github.com/ana-lan/downshift/blob/main/CONTRIBUTING.md) and [docs/architecture.md](https://github.com/ana-lan/downshift/blob/main/docs/architecture.md).
221
+
222
+ ## License
223
+
224
+ MIT
@@ -0,0 +1,181 @@
1
+ <div align="center">
2
+
3
+ <h1>Downshift</h1>
4
+
5
+ <p><strong>Cut LLM costs per PR.</strong> Downshift finds every LLM call in a Python repo, tests cheaper models against evals written for each call site, recommends the safe downgrades, and shows the projected cost impact of every pull request.</p>
6
+
7
+ <p>
8
+ <a href="https://pypi.org/project/downshift/"><img src="https://img.shields.io/pypi/v/downshift" alt="PyPI"></a>
9
+ <a href="https://pypi.org/project/downshift/"><img src="https://img.shields.io/pypi/pyversions/downshift" alt="Python"></a>
10
+ <a href="https://github.com/ana-lan/downshift/actions/workflows/ci.yml"><img src="https://github.com/ana-lan/downshift/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
11
+ <a href="https://github.com/ana-lan/downshift/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue" alt="License: MIT"></a>
12
+ </p>
13
+
14
+ <p><strong>Live demo:</strong> <a href="https://downshift-llm.vercel.app/">downshift-llm.vercel.app</a></p>
15
+
16
+ </div>
17
+
18
+ Downshift is the toolkit, [IBM Bob](https://bob.ibm.com/) is the brain. Static analysis gets most of the way. Bob closes the gaps that need real code understanding (models and prompts only known at runtime, one helper serving several features), writes the evals, applies the changes, and reviews PRs. Everything except the audit runs without Bob.
19
+
20
+ ## Quickstart
21
+
22
+ ```bash
23
+ pip install downshift
24
+ curl -O https://raw.githubusercontent.com/ana-lan/downshift/main/downshift.example.yaml
25
+ downshift scan path/to/repo
26
+ downshift estimate path/to/repo/.downshift/callsites.json -c downshift.example.yaml
27
+ ```
28
+
29
+ `scan` lists every LLM call site with its model, prompt and output type. `estimate` prices them per month with the prices and volumes in the config. Edit both to match your provider and traffic.
30
+
31
+ ## Full pipeline
32
+
33
+ ```bash
34
+ downshift scan path/to/repo # 1. find call sites
35
+ downshift evalgen --callsites path/to/repo/.downshift/callsites.json \
36
+ --out path/to/repo/.downshift/evals # 2. write evals (or use Bob)
37
+ downshift run --callsites path/to/repo/.downshift/callsites.json # 3. baseline vs cheaper models
38
+ downshift report --callsites path/to/repo/.downshift/callsites.json \
39
+ --out report.md # 4. decision per call site
40
+ downshift diff path/to/repo --base main # 5. cost change of your branch
41
+ ```
42
+
43
+ | Command | What it does |
44
+ |---|---|
45
+ | `scan` | Find every LLM call site in a repository. |
46
+ | `validate` | Check a callsites or audit file against the schema. |
47
+ | `compare` | Compare the ast scan with a Bob audit, side by side. |
48
+ | `check-evals` | Check eval files against the call sites they test. |
49
+ | `evalgen` | Generate an eval set for each call site with a configured model. |
50
+ | `run` | Run each call site's evals on the baseline and candidate models, and score them. |
51
+ | `rescore` | Re-grade saved outputs of judge-graded call sites with the configured judge. |
52
+ | `report` | Render the cost and quality report. |
53
+ | `estimate` | Project monthly LLM cost from a scan or audit file. No evals needed. |
54
+ | `diff` | Show the projected LLM cost change between two git refs. |
55
+ | `export` | Export JSON and the report for the demo web app. |
56
+
57
+ ## How it works
58
+
59
+ ```
60
+ repo ──► scan (ast) ──► Bob audit ──► evals ──► run ──► decide ──► report
61
+ static runtime per base + cheapest cost +
62
+ analysis models, call cheaper model that quality
63
+ shared site models keeps the per site
64
+ helpers quality bar
65
+ PR ──► diff (base vs head) ──► GitHub Action comment: "+$X/month"
66
+ ```
67
+
68
+ 1. **Scan.** An AST scanner finds OpenAI (v1 SDK) and Anthropic calls. It resolves models through constants, imports, env var defaults, dict lookups and parameter defaults, recovers prompt templates, and records callers.
69
+ 2. **Audit (Bob).** The Downshift Auditor mode reads only the call sites the scanner could not resolve, splits shared helpers into the features they serve, and adds purpose, output contract, difficulty and grading. Output is checked with `downshift validate`.
70
+ 3. **Evals.** 20 to 25 cases per call site, graded by exact match, JSON fields, or an LLM judge.
71
+ 4. **Run and decide.** Each call site runs on the baseline and every cheaper candidate. A candidate wins if it keeps `quality_threshold` of the baseline's quality and passes `min_pass_rate` of cases. The cheapest winner is recommended.
72
+ 5. **Report and diff.** Monthly cost before and after, per call site, from real token counts, your prices and your volumes. `diff` projects the change a branch makes, and the GitHub Action posts it on every PR.
73
+
74
+ ## Results on the demo app
75
+
76
+ SupportDesk (`examples/supportdesk`) is a synthetic support ticket app with 8 LLM features, 40 synthetic tickets and a refund policy document.
77
+
78
+ | | ast scan | Bob audit |
79
+ |---|---|---|
80
+ | Call sites / features | 7 | 8 |
81
+ | Models resolved | 6 | 8 |
82
+ | Prompts resolved | 5 | 8 |
83
+
84
+ After evals on local Qwen 2.5 models (7b baseline; 3b, 1.5b, 0.5b candidates), **3 of 8 call sites can downgrade**. Projected monthly cost goes from **$3,137.26 to $2,572.61, saving $564.65 (18%)**, while the mean pass rate moves from 68.3% to 68.9%. Full report: [`examples/supportdesk/downshift.report.md`](https://github.com/ana-lan/downshift/blob/main/examples/supportdesk/downshift.report.md). Bob then applied the decisions and opened the PR itself.
85
+
86
+ ## Case studies on real repos
87
+
88
+ | Repo | License | ast scan | Bob audit | Projected monthly cost |
89
+ |---|---|---|---|---|
90
+ | [OrchestrAI](https://github.com/ana-lan/downshift/blob/main/docs/case-study.md) | MIT | 1 site, 0 models | 5 features, 5/5 models, 5/5 prompts | $540 static-only vs $2,409 with Bob (about 4.5x) |
91
+ | [mem0](https://github.com/ana-lan/downshift/blob/main/docs/case-study-mem0.md) | Apache-2.0 | 14 sites, 1 model | 4 real features on the default path, all resolved | $307 static-only vs $2,330 with Bob (about 7.6x) |
92
+
93
+ In both repos, one shared helper hid most of the spend from static analysis. In mem0, Bob found that the reranker makes one LLM call per candidate document, about 29% of the projected bill. Both audits cost under 4 Bobcoins. See them on the web: https://downshift-llm.vercel.app/case-study/
94
+
95
+ ## GitHub Action
96
+
97
+ Add a projected cost comment to every pull request:
98
+
99
+ ```yaml
100
+ name: LLM cost diff
101
+ on: pull_request
102
+
103
+ permissions:
104
+ contents: read
105
+ pull-requests: write
106
+ issues: write
107
+
108
+ jobs:
109
+ cost-diff:
110
+ runs-on: ubuntu-latest
111
+ steps:
112
+ - uses: actions/checkout@v5
113
+ - uses: ana-lan/downshift@v0.1.1
114
+ with:
115
+ path: . # directory to scan
116
+ fail-above: "500" # optional: fail if the monthly increase is above $500
117
+ ```
118
+
119
+ Inputs: `path`, `base` (default: the PR base branch), `config` (default: `downshift.yaml` in `path`), `fail-above`, `comment`, `package`, `python-version`, `github-token`.
120
+
121
+ ![Downshift comment on a PR that adds an expensive call](https://raw.githubusercontent.com/ana-lan/downshift/main/docs/images/bad_pr_cost_diff_a.png)
122
+
123
+ ## Configuration
124
+
125
+ `downshift.yaml` sits next to the code you scan. Start from [`downshift.example.yaml`](https://github.com/ana-lan/downshift/blob/main/downshift.example.yaml).
126
+
127
+ ```yaml
128
+ version: 1
129
+ provider:
130
+ base_url: "http://localhost:11434/v1" # any OpenAI-compatible endpoint
131
+ api_key_env: null # env var holding the key, or null
132
+ models:
133
+ baseline: "qwen2.5:7b"
134
+ candidates: ["qwen2.5:3b", "qwen2.5:1.5b", "qwen2.5:0.5b"]
135
+ judge: null # defaults to the baseline
136
+ quality_threshold: 0.95
137
+ min_pass_rate: 0.80
138
+ pricing: # USD per 1M tokens
139
+ "qwen2.5:7b": { input: 2.50, output: 10.00, tier: premium }
140
+ "qwen2.5:3b": { input: 0.40, output: 1.60, tier: standard }
141
+ "qwen2.5:1.5b": { input: 0.15, output: 0.60, tier: budget }
142
+ "qwen2.5:0.5b": { input: 0.10, output: 0.40, tier: nano }
143
+ volume:
144
+ default_per_day: 20000
145
+ per_call_site: {} # e.g. "app/triage.py::classify": 50000
146
+ scan:
147
+ include: ["*.py"]
148
+ exclude: ["tests/*"]
149
+ ```
150
+
151
+ ## Using it with IBM Bob
152
+
153
+ The `.bob/` folder ships two custom modes (Downshift Auditor, Downshift Eval Writer) and their skills. The auditor can only edit the audit file, never your code. Setup and prompts: [`docs/bob.md`](https://github.com/ana-lan/downshift/blob/main/docs/bob.md). Every Bob task in this project has a screenshot in [`bob_sessions/`](https://github.com/ana-lan/downshift/tree/main/bob_sessions). In total, 11 tasks used 22.12 of 40 Bobcoins.
154
+
155
+ ## Limitations
156
+
157
+ - **Dollar figures are projections**: real token counts x prices x assumed volume, all from your config. The demo uses illustrative prices; the case studies use list prices.
158
+ - `diff` and `estimate` are static. Unresolved models are priced as the baseline, and missing `max_tokens` assumes 256 output tokens. Use them for per-PR deltas, not absolute spend.
159
+ - Python only. Detects the OpenAI v1 SDK and Anthropic messages API. Pre-v1 `openai.ChatCompletion.create`, LiteLLM and LangChain are not detected yet.
160
+ - The demo runs local Qwen 2.5 models on a laptop, not a production API benchmark. Eval inputs are synthetic.
161
+
162
+ ## Related work
163
+
164
+ [burnrate](https://pypi.org/project/burnrate/) audits LLM calls with the AST and swaps models. Downshift adds per-call-site evals that prove quality holds, a per-PR cost diff in CI, and a Bob auditor for what static analysis cannot see.
165
+
166
+ ## Development
167
+
168
+ ```bash
169
+ git clone https://github.com/ana-lan/downshift && cd downshift
170
+ python -m venv .venv && source .venv/bin/activate
171
+ pip install -e ".[dev]"
172
+ ruff format . && ruff check . && mypy src
173
+ python -m pytest # 577 tests, no network, no models (97% coverage)
174
+ python -m pytest -m integration # needs a local Ollama
175
+ ```
176
+
177
+ The web demo lives in `web/` (Next.js, static export). See [CONTRIBUTING.md](https://github.com/ana-lan/downshift/blob/main/CONTRIBUTING.md) and [docs/architecture.md](https://github.com/ana-lan/downshift/blob/main/docs/architecture.md).
178
+
179
+ ## License
180
+
181
+ MIT