Spaces:
Sleeping
Sleeping
| """LLM layer — provider-agnostic entry point. | |
| `main.py`, the tools, and the LangGraph nodes import only these dispatchers and | |
| the neutral result types; they never name a provider. Which backend runs is | |
| decided by `settings.llm_provider` ("anthropic" default | "gemini" | "local"). | |
| Provider modules are imported lazily inside each dispatcher so a deploy with | |
| only one provider's SDK/key still works. | |
| """ | |
| from __future__ import annotations | |
| from collections.abc import Callable | |
| from finrag.config import settings | |
| from finrag.llm.base import SynthesisResult, ToolCall, ToolLoopResult | |
| from finrag.retrieval.vector import RetrievedChunk | |
| __all__ = [ | |
| "synthesize", | |
| "generate_text", | |
| "run_tool_loop", | |
| "run_tool_loop_stream", | |
| "SynthesisResult", | |
| "ToolLoopResult", | |
| "ToolCall", | |
| ] | |
| def _provider() -> str: | |
| return (settings.llm_provider or "anthropic").lower() | |
| def synthesize(question: str, chunks: list[RetrievedChunk]) -> SynthesisResult: | |
| if _provider() == "gemini": | |
| from finrag.llm.gemini import synthesize_gemini | |
| return synthesize_gemini(question, chunks) | |
| if _provider() == "local": | |
| from finrag.llm.local import synthesize_local | |
| return synthesize_local(question, chunks) | |
| from finrag.llm.claude import synthesize_claude | |
| return synthesize_claude(question, chunks) | |
| def generate_text(system_instruction: str, user_text: str, **kwargs) -> str: | |
| """Single-shot text completion (planning, NL→SQL).""" | |
| if _provider() == "gemini": | |
| from finrag.llm.gemini import generate_text as _gt | |
| return _gt(system_instruction, user_text, **kwargs) | |
| if _provider() == "local": | |
| from finrag.llm.local import generate_text as _gt | |
| return _gt(system_instruction, user_text, **kwargs) | |
| from finrag.llm.claude import generate_text as _gt | |
| return _gt(system_instruction, user_text, **kwargs) | |
| def run_tool_loop(system: str, user_text: str, **kwargs) -> ToolLoopResult: | |
| """Agentic tool-calling loop — Gemini function-calling or Claude tool_use.""" | |
| if _provider() == "gemini": | |
| from finrag.llm.gemini import tool_loop | |
| return tool_loop(system, user_text, **kwargs) | |
| if _provider() == "local": | |
| from finrag.llm.local import tool_loop | |
| return tool_loop(system, user_text, **kwargs) | |
| from finrag.llm.claude import tool_loop | |
| return tool_loop(system, user_text, **kwargs) | |
| def run_tool_loop_stream( | |
| system: str, | |
| user_text: str, | |
| *, | |
| on_text: Callable[[str], None] = lambda _t: None, | |
| on_tool_call: Callable[[ToolCall], None] = lambda _c: None, | |
| **kwargs, | |
| ) -> ToolLoopResult: | |
| """Streaming agentic loop: emits text deltas (`on_text`) and live tool calls | |
| (`on_tool_call`) as they happen, returning the same ToolLoopResult. | |
| Only Claude implements true streaming. Gemini and the local backend stay | |
| non-streaming alternates, so we run their plain loop and replay the result | |
| through the callbacks once — the seam stays intact, the live demo just isn't | |
| granular.""" | |
| if _provider() in ("gemini", "local"): | |
| if _provider() == "local": | |
| from finrag.llm.local import tool_loop | |
| else: | |
| from finrag.llm.gemini import tool_loop | |
| result = tool_loop(system, user_text, **kwargs) | |
| for tc in result.tool_calls: | |
| on_tool_call(tc) | |
| if result.answer: | |
| on_text(result.answer) | |
| return result | |
| from finrag.llm.claude import tool_loop_stream | |
| return tool_loop_stream( | |
| system, user_text, on_text=on_text, on_tool_call=on_tool_call, **kwargs | |
| ) | |