Joseph Pollack commited on
Commit
2b726b3
·
unverified ·
1 Parent(s): 0467062

attempts to solve the api_key issue for huggingface , settings not appearing , set settings for audio , adds modal gpu , speech to text with mic input addon, adds graphs

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. docs/api/agents.md +1 -0
  2. docs/api/models.md +1 -0
  3. docs/api/orchestrators.md +1 -0
  4. docs/api/services.md +1 -0
  5. docs/api/tools.md +1 -0
  6. docs/architecture/agents.md +1 -0
  7. docs/architecture/middleware.md +1 -0
  8. docs/architecture/services.md +1 -0
  9. docs/architecture/tools.md +1 -0
  10. docs/contributing/code-quality.md +1 -0
  11. docs/contributing/code-style.md +1 -0
  12. docs/contributing/error-handling.md +1 -0
  13. docs/contributing/implementation-patterns.md +1 -0
  14. docs/contributing/index.md +1 -0
  15. docs/contributing/prompt-engineering.md +1 -0
  16. docs/contributing/testing.md +1 -0
  17. docs/getting-started/examples.md +1 -0
  18. docs/getting-started/installation.md +1 -0
  19. docs/getting-started/mcp-integration.md +1 -0
  20. docs/getting-started/quick-start.md +1 -0
  21. docs/implementation/IMPLEMENTATION_SUMMARY.md +1 -0
  22. docs/implementation/TTS_MODAL_IMPLEMENTATION.md +1 -0
  23. docs/license.md +1 -0
  24. docs/overview/architecture.md +1 -0
  25. docs/overview/features.md +1 -0
  26. docs/team.md +1 -0
  27. new_env.txt +1 -0
  28. src/agent_factory/agents.py +24 -16
  29. src/agent_factory/judges.py +32 -6
  30. src/agents/input_parser.py +3 -2
  31. src/agents/knowledge_gap.py +3 -2
  32. src/agents/long_writer.py +3 -2
  33. src/agents/proofreader.py +3 -2
  34. src/agents/thinking.py +3 -2
  35. src/agents/tool_selector.py +3 -2
  36. src/agents/writer.py +3 -2
  37. src/middleware/state_machine.py +1 -0
  38. src/orchestrator/graph_orchestrator.py +23 -14
  39. src/orchestrator/planner_agent.py +3 -2
  40. src/orchestrator/research_flow.py +13 -7
  41. src/orchestrator_factory.py +3 -0
  42. src/services/image_ocr.py +3 -1
  43. src/services/multimodal_processing.py +1 -0
  44. src/services/stt_gradio.py +2 -1
  45. src/tools/crawl_adapter.py +1 -0
  46. src/tools/web_search_adapter.py +1 -0
  47. src/utils/config.py +1 -67
  48. tests/unit/middleware/__init__.py +1 -0
  49. tests/unit/middleware/test_budget_tracker_phase7.py +1 -0
  50. tests/unit/middleware/test_state_machine.py +1 -0
docs/api/agents.md CHANGED
@@ -270,3 +270,4 @@ def create_input_parser_agent(model: Any | None = None) -> InputParserAgent
270
 
271
 
272
 
 
 
270
 
271
 
272
 
273
+
docs/api/models.md CHANGED
@@ -248,3 +248,4 @@ class BudgetStatus(BaseModel):
248
 
249
 
250
 
 
 
248
 
249
 
250
 
251
+
docs/api/orchestrators.md CHANGED
@@ -195,3 +195,4 @@ Runs Magentic orchestration.
195
 
196
 
197
 
 
 
195
 
196
 
197
 
198
+
docs/api/services.md CHANGED
@@ -201,3 +201,4 @@ Analyzes a hypothesis using statistical methods.
201
 
202
 
203
 
 
 
201
 
202
 
203
 
204
+
docs/api/tools.md CHANGED
@@ -235,3 +235,4 @@ Searches multiple tools in parallel.
235
 
236
 
237
 
 
 
235
 
236
 
237
 
238
+
docs/architecture/agents.md CHANGED
@@ -192,3 +192,4 @@ Factory functions:
192
 
193
 
194
 
 
 
192
 
193
 
194
 
195
+
docs/architecture/middleware.md CHANGED
@@ -142,3 +142,4 @@ All middleware components use `ContextVar` for thread-safe isolation:
142
 
143
 
144
 
 
 
142
 
143
 
144
 
145
+
docs/architecture/services.md CHANGED
@@ -142,3 +142,4 @@ if settings.has_openai_key:
142
 
143
 
144
 
 
 
142
 
143
 
144
 
145
+
docs/architecture/tools.md CHANGED
@@ -175,3 +175,4 @@ search_handler = SearchHandler(
175
 
176
 
177
 
 
 
175
 
176
 
177
 
178
+
docs/contributing/code-quality.md CHANGED
@@ -81,3 +81,4 @@ async def search(self, query: str, max_results: int = 10) -> list[Evidence]:
81
 
82
 
83
 
 
 
81
 
82
 
83
 
84
+
docs/contributing/code-style.md CHANGED
@@ -61,3 +61,4 @@ result = await loop.run_in_executor(None, cpu_bound_function, args)
61
 
62
 
63
 
 
 
61
 
62
 
63
 
64
+
docs/contributing/error-handling.md CHANGED
@@ -69,3 +69,4 @@ except httpx.HTTPError as e:
69
 
70
 
71
 
 
 
69
 
70
 
71
 
72
+
docs/contributing/implementation-patterns.md CHANGED
@@ -84,3 +84,4 @@ def get_embedding_service() -> EmbeddingService:
84
 
85
 
86
 
 
 
84
 
85
 
86
 
87
+
docs/contributing/index.md CHANGED
@@ -163,3 +163,4 @@ Thank you for contributing to DeepCritical!
163
 
164
 
165
 
 
 
163
 
164
 
165
 
166
+
docs/contributing/prompt-engineering.md CHANGED
@@ -69,3 +69,4 @@ This document outlines prompt engineering guidelines and citation validation rul
69
 
70
 
71
 
 
 
69
 
70
 
71
 
72
+
docs/contributing/testing.md CHANGED
@@ -65,3 +65,4 @@ async def test_real_pubmed_search():
65
 
66
 
67
 
 
 
65
 
66
 
67
 
68
+
docs/getting-started/examples.md CHANGED
@@ -209,3 +209,4 @@ USE_GRAPH_EXECUTION=true
209
 
210
 
211
 
 
 
209
 
210
 
211
 
212
+
docs/getting-started/installation.md CHANGED
@@ -148,3 +148,4 @@ uv run pre-commit install
148
 
149
 
150
 
 
 
148
 
149
 
150
 
151
+
docs/getting-started/mcp-integration.md CHANGED
@@ -215,3 +215,4 @@ You can configure multiple DeepCritical instances:
215
 
216
 
217
 
 
 
215
 
216
 
217
 
218
+
docs/getting-started/quick-start.md CHANGED
@@ -119,3 +119,4 @@ What are the active clinical trials investigating Alzheimer's disease treatments
119
 
120
 
121
 
 
 
119
 
120
 
121
 
122
+
docs/implementation/IMPLEMENTATION_SUMMARY.md CHANGED
@@ -178,3 +178,4 @@ Located in `src/app.py` lines 667-712:
178
 
179
 
180
 
 
 
178
 
179
 
180
 
181
+
docs/implementation/TTS_MODAL_IMPLEMENTATION.md CHANGED
@@ -132,3 +132,4 @@ To test TTS:
132
 
133
 
134
 
 
 
132
 
133
 
134
 
135
+
docs/license.md CHANGED
@@ -39,3 +39,4 @@ SOFTWARE.
39
 
40
 
41
 
 
 
39
 
40
 
41
 
42
+
docs/overview/architecture.md CHANGED
@@ -196,3 +196,4 @@ The system supports complex research workflows through:
196
 
197
 
198
 
 
 
196
 
197
 
198
 
199
+
docs/overview/features.md CHANGED
@@ -148,3 +148,4 @@ DeepCritical provides a comprehensive set of features for AI-assisted research:
148
 
149
 
150
 
 
 
148
 
149
 
150
 
151
+
docs/team.md CHANGED
@@ -44,3 +44,4 @@ We welcome contributions! See the [Contributing Guide](contributing/index.md) fo
44
 
45
 
46
 
 
 
44
 
45
 
46
 
47
+
new_env.txt CHANGED
@@ -94,3 +94,4 @@ MODAL_TOKEN_SECRET=your_modal_token_secret_here
94
 
95
 
96
 
 
 
94
 
95
 
96
 
97
+
src/agent_factory/agents.py CHANGED
@@ -27,12 +27,13 @@ if TYPE_CHECKING:
27
  logger = structlog.get_logger()
28
 
29
 
30
- def create_input_parser_agent(model: Any | None = None) -> "InputParserAgent":
31
  """
32
  Create input parser agent for query analysis and research mode detection.
33
 
34
  Args:
35
  model: Optional Pydantic AI model. If None, uses settings default.
 
36
 
37
  Returns:
38
  Configured InputParserAgent instance
@@ -44,18 +45,19 @@ def create_input_parser_agent(model: Any | None = None) -> "InputParserAgent":
44
 
45
  try:
46
  logger.debug("Creating input parser agent")
47
- return _create_agent(model=model)
48
  except Exception as e:
49
  logger.error("Failed to create input parser agent", error=str(e))
50
  raise ConfigurationError(f"Failed to create input parser agent: {e}") from e
51
 
52
 
53
- def create_planner_agent(model: Any | None = None) -> "PlannerAgent":
54
  """
55
  Create planner agent with web search and crawl tools.
56
 
57
  Args:
58
  model: Optional Pydantic AI model. If None, uses settings default.
 
59
 
60
  Returns:
61
  Configured PlannerAgent instance
@@ -68,18 +70,19 @@ def create_planner_agent(model: Any | None = None) -> "PlannerAgent":
68
 
69
  try:
70
  logger.debug("Creating planner agent")
71
- return _create_planner_agent(model=model)
72
  except Exception as e:
73
  logger.error("Failed to create planner agent", error=str(e))
74
  raise ConfigurationError(f"Failed to create planner agent: {e}") from e
75
 
76
 
77
- def create_knowledge_gap_agent(model: Any | None = None) -> "KnowledgeGapAgent":
78
  """
79
  Create knowledge gap agent for evaluating research completeness.
80
 
81
  Args:
82
  model: Optional Pydantic AI model. If None, uses settings default.
 
83
 
84
  Returns:
85
  Configured KnowledgeGapAgent instance
@@ -91,18 +94,19 @@ def create_knowledge_gap_agent(model: Any | None = None) -> "KnowledgeGapAgent":
91
 
92
  try:
93
  logger.debug("Creating knowledge gap agent")
94
- return _create_agent(model=model)
95
  except Exception as e:
96
  logger.error("Failed to create knowledge gap agent", error=str(e))
97
  raise ConfigurationError(f"Failed to create knowledge gap agent: {e}") from e
98
 
99
 
100
- def create_tool_selector_agent(model: Any | None = None) -> "ToolSelectorAgent":
101
  """
102
  Create tool selector agent for choosing tools to address gaps.
103
 
104
  Args:
105
  model: Optional Pydantic AI model. If None, uses settings default.
 
106
 
107
  Returns:
108
  Configured ToolSelectorAgent instance
@@ -114,18 +118,19 @@ def create_tool_selector_agent(model: Any | None = None) -> "ToolSelectorAgent":
114
 
115
  try:
116
  logger.debug("Creating tool selector agent")
117
- return _create_agent(model=model)
118
  except Exception as e:
119
  logger.error("Failed to create tool selector agent", error=str(e))
120
  raise ConfigurationError(f"Failed to create tool selector agent: {e}") from e
121
 
122
 
123
- def create_thinking_agent(model: Any | None = None) -> "ThinkingAgent":
124
  """
125
  Create thinking agent for generating observations.
126
 
127
  Args:
128
  model: Optional Pydantic AI model. If None, uses settings default.
 
129
 
130
  Returns:
131
  Configured ThinkingAgent instance
@@ -137,18 +142,19 @@ def create_thinking_agent(model: Any | None = None) -> "ThinkingAgent":
137
 
138
  try:
139
  logger.debug("Creating thinking agent")
140
- return _create_agent(model=model)
141
  except Exception as e:
142
  logger.error("Failed to create thinking agent", error=str(e))
143
  raise ConfigurationError(f"Failed to create thinking agent: {e}") from e
144
 
145
 
146
- def create_writer_agent(model: Any | None = None) -> "WriterAgent":
147
  """
148
  Create writer agent for generating final reports.
149
 
150
  Args:
151
  model: Optional Pydantic AI model. If None, uses settings default.
 
152
 
153
  Returns:
154
  Configured WriterAgent instance
@@ -160,18 +166,19 @@ def create_writer_agent(model: Any | None = None) -> "WriterAgent":
160
 
161
  try:
162
  logger.debug("Creating writer agent")
163
- return _create_agent(model=model)
164
  except Exception as e:
165
  logger.error("Failed to create writer agent", error=str(e))
166
  raise ConfigurationError(f"Failed to create writer agent: {e}") from e
167
 
168
 
169
- def create_long_writer_agent(model: Any | None = None) -> "LongWriterAgent":
170
  """
171
  Create long writer agent for iteratively writing report sections.
172
 
173
  Args:
174
  model: Optional Pydantic AI model. If None, uses settings default.
 
175
 
176
  Returns:
177
  Configured LongWriterAgent instance
@@ -183,18 +190,19 @@ def create_long_writer_agent(model: Any | None = None) -> "LongWriterAgent":
183
 
184
  try:
185
  logger.debug("Creating long writer agent")
186
- return _create_agent(model=model)
187
  except Exception as e:
188
  logger.error("Failed to create long writer agent", error=str(e))
189
  raise ConfigurationError(f"Failed to create long writer agent: {e}") from e
190
 
191
 
192
- def create_proofreader_agent(model: Any | None = None) -> "ProofreaderAgent":
193
  """
194
  Create proofreader agent for finalizing report drafts.
195
 
196
  Args:
197
  model: Optional Pydantic AI model. If None, uses settings default.
 
198
 
199
  Returns:
200
  Configured ProofreaderAgent instance
@@ -206,7 +214,7 @@ def create_proofreader_agent(model: Any | None = None) -> "ProofreaderAgent":
206
 
207
  try:
208
  logger.debug("Creating proofreader agent")
209
- return _create_agent(model=model)
210
  except Exception as e:
211
  logger.error("Failed to create proofreader agent", error=str(e))
212
  raise ConfigurationError(f"Failed to create proofreader agent: {e}") from e
 
27
  logger = structlog.get_logger()
28
 
29
 
30
+ def create_input_parser_agent(model: Any | None = None, oauth_token: str | None = None) -> "InputParserAgent":
31
  """
32
  Create input parser agent for query analysis and research mode detection.
33
 
34
  Args:
35
  model: Optional Pydantic AI model. If None, uses settings default.
36
+ oauth_token: Optional OAuth token from HuggingFace login (takes priority over env vars)
37
 
38
  Returns:
39
  Configured InputParserAgent instance
 
45
 
46
  try:
47
  logger.debug("Creating input parser agent")
48
+ return _create_agent(model=model, oauth_token=oauth_token)
49
  except Exception as e:
50
  logger.error("Failed to create input parser agent", error=str(e))
51
  raise ConfigurationError(f"Failed to create input parser agent: {e}") from e
52
 
53
 
54
+ def create_planner_agent(model: Any | None = None, oauth_token: str | None = None) -> "PlannerAgent":
55
  """
56
  Create planner agent with web search and crawl tools.
57
 
58
  Args:
59
  model: Optional Pydantic AI model. If None, uses settings default.
60
+ oauth_token: Optional OAuth token from HuggingFace login (takes priority over env vars)
61
 
62
  Returns:
63
  Configured PlannerAgent instance
 
70
 
71
  try:
72
  logger.debug("Creating planner agent")
73
+ return _create_planner_agent(model=model, oauth_token=oauth_token)
74
  except Exception as e:
75
  logger.error("Failed to create planner agent", error=str(e))
76
  raise ConfigurationError(f"Failed to create planner agent: {e}") from e
77
 
78
 
79
+ def create_knowledge_gap_agent(model: Any | None = None, oauth_token: str | None = None) -> "KnowledgeGapAgent":
80
  """
81
  Create knowledge gap agent for evaluating research completeness.
82
 
83
  Args:
84
  model: Optional Pydantic AI model. If None, uses settings default.
85
+ oauth_token: Optional OAuth token from HuggingFace login (takes priority over env vars)
86
 
87
  Returns:
88
  Configured KnowledgeGapAgent instance
 
94
 
95
  try:
96
  logger.debug("Creating knowledge gap agent")
97
+ return _create_agent(model=model, oauth_token=oauth_token)
98
  except Exception as e:
99
  logger.error("Failed to create knowledge gap agent", error=str(e))
100
  raise ConfigurationError(f"Failed to create knowledge gap agent: {e}") from e
101
 
102
 
103
+ def create_tool_selector_agent(model: Any | None = None, oauth_token: str | None = None) -> "ToolSelectorAgent":
104
  """
105
  Create tool selector agent for choosing tools to address gaps.
106
 
107
  Args:
108
  model: Optional Pydantic AI model. If None, uses settings default.
109
+ oauth_token: Optional OAuth token from HuggingFace login (takes priority over env vars)
110
 
111
  Returns:
112
  Configured ToolSelectorAgent instance
 
118
 
119
  try:
120
  logger.debug("Creating tool selector agent")
121
+ return _create_agent(model=model, oauth_token=oauth_token)
122
  except Exception as e:
123
  logger.error("Failed to create tool selector agent", error=str(e))
124
  raise ConfigurationError(f"Failed to create tool selector agent: {e}") from e
125
 
126
 
127
+ def create_thinking_agent(model: Any | None = None, oauth_token: str | None = None) -> "ThinkingAgent":
128
  """
129
  Create thinking agent for generating observations.
130
 
131
  Args:
132
  model: Optional Pydantic AI model. If None, uses settings default.
133
+ oauth_token: Optional OAuth token from HuggingFace login (takes priority over env vars)
134
 
135
  Returns:
136
  Configured ThinkingAgent instance
 
142
 
143
  try:
144
  logger.debug("Creating thinking agent")
145
+ return _create_agent(model=model, oauth_token=oauth_token)
146
  except Exception as e:
147
  logger.error("Failed to create thinking agent", error=str(e))
148
  raise ConfigurationError(f"Failed to create thinking agent: {e}") from e
149
 
150
 
151
+ def create_writer_agent(model: Any | None = None, oauth_token: str | None = None) -> "WriterAgent":
152
  """
153
  Create writer agent for generating final reports.
154
 
155
  Args:
156
  model: Optional Pydantic AI model. If None, uses settings default.
157
+ oauth_token: Optional OAuth token from HuggingFace login (takes priority over env vars)
158
 
159
  Returns:
160
  Configured WriterAgent instance
 
166
 
167
  try:
168
  logger.debug("Creating writer agent")
169
+ return _create_agent(model=model, oauth_token=oauth_token)
170
  except Exception as e:
171
  logger.error("Failed to create writer agent", error=str(e))
172
  raise ConfigurationError(f"Failed to create writer agent: {e}") from e
173
 
174
 
175
+ def create_long_writer_agent(model: Any | None = None, oauth_token: str | None = None) -> "LongWriterAgent":
176
  """
177
  Create long writer agent for iteratively writing report sections.
178
 
179
  Args:
180
  model: Optional Pydantic AI model. If None, uses settings default.
181
+ oauth_token: Optional OAuth token from HuggingFace login (takes priority over env vars)
182
 
183
  Returns:
184
  Configured LongWriterAgent instance
 
190
 
191
  try:
192
  logger.debug("Creating long writer agent")
193
+ return _create_agent(model=model, oauth_token=oauth_token)
194
  except Exception as e:
195
  logger.error("Failed to create long writer agent", error=str(e))
196
  raise ConfigurationError(f"Failed to create long writer agent: {e}") from e
197
 
198
 
199
+ def create_proofreader_agent(model: Any | None = None, oauth_token: str | None = None) -> "ProofreaderAgent":
200
  """
201
  Create proofreader agent for finalizing report drafts.
202
 
203
  Args:
204
  model: Optional Pydantic AI model. If None, uses settings default.
205
+ oauth_token: Optional OAuth token from HuggingFace login (takes priority over env vars)
206
 
207
  Returns:
208
  Configured ProofreaderAgent instance
 
214
 
215
  try:
216
  logger.debug("Creating proofreader agent")
217
+ return _create_agent(model=model, oauth_token=oauth_token)
218
  except Exception as e:
219
  logger.error("Failed to create proofreader agent", error=str(e))
220
  raise ConfigurationError(f"Failed to create proofreader agent: {e}") from e
src/agent_factory/judges.py CHANGED
@@ -32,34 +32,60 @@ def get_model(oauth_token: str | None = None) -> Any:
32
  Explicitly passes API keys from settings to avoid requiring
33
  users to export environment variables manually.
34
 
 
 
 
35
  Args:
36
  oauth_token: Optional OAuth token from HuggingFace login (takes priority over env vars)
37
  """
38
- llm_provider = settings.llm_provider
39
-
40
  # Priority: oauth_token > env vars
41
  effective_hf_token = oauth_token or settings.hf_token or settings.huggingface_api_key
42
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
  if llm_provider == "anthropic":
 
 
 
 
 
 
44
  provider = AnthropicProvider(api_key=settings.anthropic_api_key)
45
  return AnthropicModel(settings.anthropic_model, provider=provider)
46
 
47
  if llm_provider == "huggingface":
48
- # Free tier - uses OAuth token or HF_TOKEN from environment if available
49
  model_name = settings.huggingface_model or "meta-llama/Llama-3.1-8B-Instruct"
50
- hf_provider = HuggingFaceProvider(api_key=effective_hf_token)
51
  return HuggingFaceModel(model_name, provider=hf_provider)
52
 
53
  if llm_provider == "openai":
 
 
 
 
 
 
54
  openai_provider = OpenAIProvider(api_key=settings.openai_api_key)
55
  return OpenAIModel(settings.openai_model, provider=openai_provider)
56
 
57
  # Default to HuggingFace if provider is unknown or not specified
58
- if llm_provider != "huggingface":
59
  logger.warning("Unknown LLM provider, defaulting to HuggingFace", provider=llm_provider)
60
 
61
  model_name = settings.huggingface_model or "meta-llama/Llama-3.1-8B-Instruct"
62
- hf_provider = HuggingFaceProvider(api_key=effective_hf_token)
63
  return HuggingFaceModel(model_name, provider=hf_provider)
64
 
65
 
 
32
  Explicitly passes API keys from settings to avoid requiring
33
  users to export environment variables manually.
34
 
35
+ Priority: If OAuth token is available, prefer HuggingFace (even if provider is set to OpenAI).
36
+ This ensures users logged in via HuggingFace Spaces get the free tier.
37
+
38
  Args:
39
  oauth_token: Optional OAuth token from HuggingFace login (takes priority over env vars)
40
  """
 
 
41
  # Priority: oauth_token > env vars
42
  effective_hf_token = oauth_token or settings.hf_token or settings.huggingface_api_key
43
 
44
+ # If OAuth token is available, prefer HuggingFace (free tier on Spaces)
45
+ if effective_hf_token:
46
+ model_name = settings.huggingface_model or "meta-llama/Llama-3.1-8B-Instruct"
47
+ hf_provider = HuggingFaceProvider(api_key=effective_hf_token)
48
+ logger.info(
49
+ "using_huggingface_with_token",
50
+ has_oauth=bool(oauth_token),
51
+ model=model_name,
52
+ )
53
+ return HuggingFaceModel(model_name, provider=hf_provider)
54
+
55
+ llm_provider = settings.llm_provider
56
+
57
  if llm_provider == "anthropic":
58
+ if not settings.anthropic_api_key:
59
+ logger.warning("Anthropic provider selected but no API key available, defaulting to HuggingFace")
60
+ # Fallback to HuggingFace without token (public models)
61
+ model_name = settings.huggingface_model or "meta-llama/Llama-3.1-8B-Instruct"
62
+ hf_provider = HuggingFaceProvider(api_key=None)
63
+ return HuggingFaceModel(model_name, provider=hf_provider)
64
  provider = AnthropicProvider(api_key=settings.anthropic_api_key)
65
  return AnthropicModel(settings.anthropic_model, provider=provider)
66
 
67
  if llm_provider == "huggingface":
68
+ # No token available, use public models
69
  model_name = settings.huggingface_model or "meta-llama/Llama-3.1-8B-Instruct"
70
+ hf_provider = HuggingFaceProvider(api_key=None)
71
  return HuggingFaceModel(model_name, provider=hf_provider)
72
 
73
  if llm_provider == "openai":
74
+ if not settings.openai_api_key:
75
+ logger.warning("OpenAI provider selected but no API key available, defaulting to HuggingFace")
76
+ # Fallback to HuggingFace without token (public models)
77
+ model_name = settings.huggingface_model or "meta-llama/Llama-3.1-8B-Instruct"
78
+ hf_provider = HuggingFaceProvider(api_key=None)
79
+ return HuggingFaceModel(model_name, provider=hf_provider)
80
  openai_provider = OpenAIProvider(api_key=settings.openai_api_key)
81
  return OpenAIModel(settings.openai_model, provider=openai_provider)
82
 
83
  # Default to HuggingFace if provider is unknown or not specified
84
+ if llm_provider not in ("huggingface", "openai", "anthropic"):
85
  logger.warning("Unknown LLM provider, defaulting to HuggingFace", provider=llm_provider)
86
 
87
  model_name = settings.huggingface_model or "meta-llama/Llama-3.1-8B-Instruct"
88
+ hf_provider = HuggingFaceProvider(api_key=None) # Public models
89
  return HuggingFaceModel(model_name, provider=hf_provider)
90
 
91
 
src/agents/input_parser.py CHANGED
@@ -152,12 +152,13 @@ class InputParserAgent:
152
  )
153
 
154
 
155
- def create_input_parser_agent(model: Any | None = None) -> InputParserAgent:
156
  """
157
  Factory function to create an input parser agent.
158
 
159
  Args:
160
  model: Optional Pydantic AI model. If None, uses settings default.
 
161
 
162
  Returns:
163
  Configured InputParserAgent instance
@@ -168,7 +169,7 @@ def create_input_parser_agent(model: Any | None = None) -> InputParserAgent:
168
  try:
169
  # Get model from settings if not provided
170
  if model is None:
171
- model = get_model()
172
 
173
  # Create and return input parser agent
174
  return InputParserAgent(model=model)
 
152
  )
153
 
154
 
155
+ def create_input_parser_agent(model: Any | None = None, oauth_token: str | None = None) -> InputParserAgent:
156
  """
157
  Factory function to create an input parser agent.
158
 
159
  Args:
160
  model: Optional Pydantic AI model. If None, uses settings default.
161
+ oauth_token: Optional OAuth token from HuggingFace login (takes priority over env vars)
162
 
163
  Returns:
164
  Configured InputParserAgent instance
 
169
  try:
170
  # Get model from settings if not provided
171
  if model is None:
172
+ model = get_model(oauth_token=oauth_token)
173
 
174
  # Create and return input parser agent
175
  return InputParserAgent(model=model)
src/agents/knowledge_gap.py CHANGED
@@ -132,12 +132,13 @@ HISTORY OF ACTIONS, FINDINGS AND THOUGHTS:
132
  )
133
 
134
 
135
- def create_knowledge_gap_agent(model: Any | None = None) -> KnowledgeGapAgent:
136
  """
137
  Factory function to create a knowledge gap agent.
138
 
139
  Args:
140
  model: Optional Pydantic AI model. If None, uses settings default.
 
141
 
142
  Returns:
143
  Configured KnowledgeGapAgent instance
@@ -147,7 +148,7 @@ def create_knowledge_gap_agent(model: Any | None = None) -> KnowledgeGapAgent:
147
  """
148
  try:
149
  if model is None:
150
- model = get_model()
151
 
152
  return KnowledgeGapAgent(model=model)
153
 
 
132
  )
133
 
134
 
135
+ def create_knowledge_gap_agent(model: Any | None = None, oauth_token: str | None = None) -> KnowledgeGapAgent:
136
  """
137
  Factory function to create a knowledge gap agent.
138
 
139
  Args:
140
  model: Optional Pydantic AI model. If None, uses settings default.
141
+ oauth_token: Optional OAuth token from HuggingFace login (takes priority over env vars)
142
 
143
  Returns:
144
  Configured KnowledgeGapAgent instance
 
148
  """
149
  try:
150
  if model is None:
151
+ model = get_model(oauth_token=oauth_token)
152
 
153
  return KnowledgeGapAgent(model=model)
154
 
src/agents/long_writer.py CHANGED
@@ -407,12 +407,13 @@ class LongWriterAgent:
407
  return re.sub(r"^(#+)\s(.+)$", adjust_heading_level, section_markdown, flags=re.MULTILINE)
408
 
409
 
410
- def create_long_writer_agent(model: Any | None = None) -> LongWriterAgent:
411
  """
412
  Factory function to create a long writer agent.
413
 
414
  Args:
415
  model: Optional Pydantic AI model. If None, uses settings default.
 
416
 
417
  Returns:
418
  Configured LongWriterAgent instance
@@ -422,7 +423,7 @@ def create_long_writer_agent(model: Any | None = None) -> LongWriterAgent:
422
  """
423
  try:
424
  if model is None:
425
- model = get_model()
426
 
427
  return LongWriterAgent(model=model)
428
 
 
407
  return re.sub(r"^(#+)\s(.+)$", adjust_heading_level, section_markdown, flags=re.MULTILINE)
408
 
409
 
410
+ def create_long_writer_agent(model: Any | None = None, oauth_token: str | None = None) -> LongWriterAgent:
411
  """
412
  Factory function to create a long writer agent.
413
 
414
  Args:
415
  model: Optional Pydantic AI model. If None, uses settings default.
416
+ oauth_token: Optional OAuth token from HuggingFace login (takes priority over env vars)
417
 
418
  Returns:
419
  Configured LongWriterAgent instance
 
423
  """
424
  try:
425
  if model is None:
426
+ model = get_model(oauth_token=oauth_token)
427
 
428
  return LongWriterAgent(model=model)
429
 
src/agents/proofreader.py CHANGED
@@ -181,12 +181,13 @@ REPORT DRAFT:
181
  return f"# Research Report\n\n## Query\n{query}\n\n" + "\n\n".join(sections)
182
 
183
 
184
- def create_proofreader_agent(model: Any | None = None) -> ProofreaderAgent:
185
  """
186
  Factory function to create a proofreader agent.
187
 
188
  Args:
189
  model: Optional Pydantic AI model. If None, uses settings default.
 
190
 
191
  Returns:
192
  Configured ProofreaderAgent instance
@@ -196,7 +197,7 @@ def create_proofreader_agent(model: Any | None = None) -> ProofreaderAgent:
196
  """
197
  try:
198
  if model is None:
199
- model = get_model()
200
 
201
  return ProofreaderAgent(model=model)
202
 
 
181
  return f"# Research Report\n\n## Query\n{query}\n\n" + "\n\n".join(sections)
182
 
183
 
184
+ def create_proofreader_agent(model: Any | None = None, oauth_token: str | None = None) -> ProofreaderAgent:
185
  """
186
  Factory function to create a proofreader agent.
187
 
188
  Args:
189
  model: Optional Pydantic AI model. If None, uses settings default.
190
+ oauth_token: Optional OAuth token from HuggingFace login (takes priority over env vars)
191
 
192
  Returns:
193
  Configured ProofreaderAgent instance
 
197
  """
198
  try:
199
  if model is None:
200
+ model = get_model(oauth_token=oauth_token)
201
 
202
  return ProofreaderAgent(model=model)
203
 
src/agents/thinking.py CHANGED
@@ -124,12 +124,13 @@ HISTORY OF ACTIONS, FINDINGS AND THOUGHTS:
124
  return f"Starting iteration {iteration}. Need to gather information about: {query}"
125
 
126
 
127
- def create_thinking_agent(model: Any | None = None) -> ThinkingAgent:
128
  """
129
  Factory function to create a thinking agent.
130
 
131
  Args:
132
  model: Optional Pydantic AI model. If None, uses settings default.
 
133
 
134
  Returns:
135
  Configured ThinkingAgent instance
@@ -139,7 +140,7 @@ def create_thinking_agent(model: Any | None = None) -> ThinkingAgent:
139
  """
140
  try:
141
  if model is None:
142
- model = get_model()
143
 
144
  return ThinkingAgent(model=model)
145
 
 
124
  return f"Starting iteration {iteration}. Need to gather information about: {query}"
125
 
126
 
127
+ def create_thinking_agent(model: Any | None = None, oauth_token: str | None = None) -> ThinkingAgent:
128
  """
129
  Factory function to create a thinking agent.
130
 
131
  Args:
132
  model: Optional Pydantic AI model. If None, uses settings default.
133
+ oauth_token: Optional OAuth token from HuggingFace login (takes priority over env vars)
134
 
135
  Returns:
136
  Configured ThinkingAgent instance
 
140
  """
141
  try:
142
  if model is None:
143
+ model = get_model(oauth_token=oauth_token)
144
 
145
  return ThinkingAgent(model=model)
146
 
src/agents/tool_selector.py CHANGED
@@ -144,12 +144,13 @@ HISTORY OF ACTIONS, FINDINGS AND THOUGHTS:
144
  )
145
 
146
 
147
- def create_tool_selector_agent(model: Any | None = None) -> ToolSelectorAgent:
148
  """
149
  Factory function to create a tool selector agent.
150
 
151
  Args:
152
  model: Optional Pydantic AI model. If None, uses settings default.
 
153
 
154
  Returns:
155
  Configured ToolSelectorAgent instance
@@ -159,7 +160,7 @@ def create_tool_selector_agent(model: Any | None = None) -> ToolSelectorAgent:
159
  """
160
  try:
161
  if model is None:
162
- model = get_model()
163
 
164
  return ToolSelectorAgent(model=model)
165
 
 
144
  )
145
 
146
 
147
+ def create_tool_selector_agent(model: Any | None = None, oauth_token: str | None = None) -> ToolSelectorAgent:
148
  """
149
  Factory function to create a tool selector agent.
150
 
151
  Args:
152
  model: Optional Pydantic AI model. If None, uses settings default.
153
+ oauth_token: Optional OAuth token from HuggingFace login (takes priority over env vars)
154
 
155
  Returns:
156
  Configured ToolSelectorAgent instance
 
160
  """
161
  try:
162
  if model is None:
163
+ model = get_model(oauth_token=oauth_token)
164
 
165
  return ToolSelectorAgent(model=model)
166
 
src/agents/writer.py CHANGED
@@ -185,12 +185,13 @@ FINDINGS:
185
  )
186
 
187
 
188
- def create_writer_agent(model: Any | None = None) -> WriterAgent:
189
  """
190
  Factory function to create a writer agent.
191
 
192
  Args:
193
  model: Optional Pydantic AI model. If None, uses settings default.
 
194
 
195
  Returns:
196
  Configured WriterAgent instance
@@ -200,7 +201,7 @@ def create_writer_agent(model: Any | None = None) -> WriterAgent:
200
  """
201
  try:
202
  if model is None:
203
- model = get_model()
204
 
205
  return WriterAgent(model=model)
206
 
 
185
  )
186
 
187
 
188
+ def create_writer_agent(model: Any | None = None, oauth_token: str | None = None) -> WriterAgent:
189
  """
190
  Factory function to create a writer agent.
191
 
192
  Args:
193
  model: Optional Pydantic AI model. If None, uses settings default.
194
+ oauth_token: Optional OAuth token from HuggingFace login (takes priority over env vars)
195
 
196
  Returns:
197
  Configured WriterAgent instance
 
201
  """
202
  try:
203
  if model is None:
204
+ model = get_model(oauth_token=oauth_token)
205
 
206
  return WriterAgent(model=model)
207
 
src/middleware/state_machine.py CHANGED
@@ -133,3 +133,4 @@ def get_workflow_state() -> WorkflowState:
133
 
134
 
135
 
 
 
133
 
134
 
135
 
136
+
src/orchestrator/graph_orchestrator.py CHANGED
@@ -124,6 +124,7 @@ class GraphOrchestrator:
124
  use_graph: bool = True,
125
  search_handler: SearchHandlerProtocol | None = None,
126
  judge_handler: JudgeHandlerProtocol | None = None,
 
127
  ) -> None:
128
  """
129
  Initialize graph orchestrator.
@@ -135,6 +136,7 @@ class GraphOrchestrator:
135
  use_graph: Whether to use graph execution (True) or agent chains (False)
136
  search_handler: Optional search handler for tool execution
137
  judge_handler: Optional judge handler for evidence assessment
 
138
  """
139
  self.mode = mode
140
  self.max_iterations = max_iterations
@@ -142,6 +144,7 @@ class GraphOrchestrator:
142
  self.use_graph = use_graph
143
  self.search_handler = search_handler
144
  self.judge_handler = judge_handler
 
145
  self.logger = logger
146
 
147
  # Initialize flows (for backward compatibility)
@@ -256,6 +259,7 @@ class GraphOrchestrator:
256
  max_iterations=self.max_iterations,
257
  max_time_minutes=self.max_time_minutes,
258
  judge_handler=self.judge_handler,
 
259
  )
260
 
261
  try:
@@ -291,6 +295,7 @@ class GraphOrchestrator:
291
  self._deep_flow = DeepResearchFlow(
292
  max_iterations=self.max_iterations,
293
  max_time_minutes=self.max_time_minutes,
 
294
  )
295
 
296
  try:
@@ -322,11 +327,11 @@ class GraphOrchestrator:
322
  Constructed ResearchGraph
323
  """
324
  if mode == "iterative":
325
- # Get agents
326
- knowledge_gap_agent = create_knowledge_gap_agent()
327
- tool_selector_agent = create_tool_selector_agent()
328
- thinking_agent = create_thinking_agent()
329
- writer_agent = create_writer_agent()
330
 
331
  # Create graph
332
  graph = create_iterative_graph(
@@ -336,13 +341,13 @@ class GraphOrchestrator:
336
  writer_agent=writer_agent.agent,
337
  )
338
  else: # deep
339
- # Get agents
340
- planner_agent = create_planner_agent()
341
- knowledge_gap_agent = create_knowledge_gap_agent()
342
- tool_selector_agent = create_tool_selector_agent()
343
- thinking_agent = create_thinking_agent()
344
- writer_agent = create_writer_agent()
345
- long_writer_agent = create_long_writer_agent()
346
 
347
  # Create graph
348
  graph = create_deep_graph(
@@ -610,7 +615,7 @@ class GraphOrchestrator:
610
  )
611
 
612
  # Get LongWriterAgent instance and call write_report directly
613
- long_writer_agent = create_long_writer_agent()
614
  final_report = await long_writer_agent.write_report(
615
  original_query=query,
616
  report_title=report_plan.report_title,
@@ -906,6 +911,7 @@ class GraphOrchestrator:
906
  verbose=False, # Less verbose in parallel execution
907
  use_graph=False, # Use agent chains for section research
908
  judge_handler=self.judge_handler or judge_handler,
 
909
  )
910
 
911
  # Run research for this section
@@ -1008,7 +1014,7 @@ class GraphOrchestrator:
1008
  """
1009
  try:
1010
  # Use input parser agent for intelligent mode detection
1011
- input_parser = create_input_parser_agent()
1012
  parsed_query = await input_parser.parse(query)
1013
  self.logger.info(
1014
  "Research mode detected by input parser",
@@ -1048,6 +1054,7 @@ def create_graph_orchestrator(
1048
  use_graph: bool = True,
1049
  search_handler: SearchHandlerProtocol | None = None,
1050
  judge_handler: JudgeHandlerProtocol | None = None,
 
1051
  ) -> GraphOrchestrator:
1052
  """
1053
  Factory function to create a graph orchestrator.
@@ -1059,6 +1066,7 @@ def create_graph_orchestrator(
1059
  use_graph: Whether to use graph execution (True) or agent chains (False)
1060
  search_handler: Optional search handler for tool execution
1061
  judge_handler: Optional judge handler for evidence assessment
 
1062
 
1063
  Returns:
1064
  Configured GraphOrchestrator instance
@@ -1070,4 +1078,5 @@ def create_graph_orchestrator(
1070
  use_graph=use_graph,
1071
  search_handler=search_handler,
1072
  judge_handler=judge_handler,
 
1073
  )
 
124
  use_graph: bool = True,
125
  search_handler: SearchHandlerProtocol | None = None,
126
  judge_handler: JudgeHandlerProtocol | None = None,
127
+ oauth_token: str | None = None,
128
  ) -> None:
129
  """
130
  Initialize graph orchestrator.
 
136
  use_graph: Whether to use graph execution (True) or agent chains (False)
137
  search_handler: Optional search handler for tool execution
138
  judge_handler: Optional judge handler for evidence assessment
139
+ oauth_token: Optional OAuth token from HuggingFace login (takes priority over env vars)
140
  """
141
  self.mode = mode
142
  self.max_iterations = max_iterations
 
144
  self.use_graph = use_graph
145
  self.search_handler = search_handler
146
  self.judge_handler = judge_handler
147
+ self.oauth_token = oauth_token
148
  self.logger = logger
149
 
150
  # Initialize flows (for backward compatibility)
 
259
  max_iterations=self.max_iterations,
260
  max_time_minutes=self.max_time_minutes,
261
  judge_handler=self.judge_handler,
262
+ oauth_token=self.oauth_token,
263
  )
264
 
265
  try:
 
295
  self._deep_flow = DeepResearchFlow(
296
  max_iterations=self.max_iterations,
297
  max_time_minutes=self.max_time_minutes,
298
+ oauth_token=self.oauth_token,
299
  )
300
 
301
  try:
 
327
  Constructed ResearchGraph
328
  """
329
  if mode == "iterative":
330
+ # Get agents - pass OAuth token for HuggingFace authentication
331
+ knowledge_gap_agent = create_knowledge_gap_agent(oauth_token=self.oauth_token)
332
+ tool_selector_agent = create_tool_selector_agent(oauth_token=self.oauth_token)
333
+ thinking_agent = create_thinking_agent(oauth_token=self.oauth_token)
334
+ writer_agent = create_writer_agent(oauth_token=self.oauth_token)
335
 
336
  # Create graph
337
  graph = create_iterative_graph(
 
341
  writer_agent=writer_agent.agent,
342
  )
343
  else: # deep
344
+ # Get agents - pass OAuth token for HuggingFace authentication
345
+ planner_agent = create_planner_agent(oauth_token=self.oauth_token)
346
+ knowledge_gap_agent = create_knowledge_gap_agent(oauth_token=self.oauth_token)
347
+ tool_selector_agent = create_tool_selector_agent(oauth_token=self.oauth_token)
348
+ thinking_agent = create_thinking_agent(oauth_token=self.oauth_token)
349
+ writer_agent = create_writer_agent(oauth_token=self.oauth_token)
350
+ long_writer_agent = create_long_writer_agent(oauth_token=self.oauth_token)
351
 
352
  # Create graph
353
  graph = create_deep_graph(
 
615
  )
616
 
617
  # Get LongWriterAgent instance and call write_report directly
618
+ long_writer_agent = create_long_writer_agent(oauth_token=self.oauth_token)
619
  final_report = await long_writer_agent.write_report(
620
  original_query=query,
621
  report_title=report_plan.report_title,
 
911
  verbose=False, # Less verbose in parallel execution
912
  use_graph=False, # Use agent chains for section research
913
  judge_handler=self.judge_handler or judge_handler,
914
+ oauth_token=self.oauth_token,
915
  )
916
 
917
  # Run research for this section
 
1014
  """
1015
  try:
1016
  # Use input parser agent for intelligent mode detection
1017
+ input_parser = create_input_parser_agent(oauth_token=self.oauth_token)
1018
  parsed_query = await input_parser.parse(query)
1019
  self.logger.info(
1020
  "Research mode detected by input parser",
 
1054
  use_graph: bool = True,
1055
  search_handler: SearchHandlerProtocol | None = None,
1056
  judge_handler: JudgeHandlerProtocol | None = None,
1057
+ oauth_token: str | None = None,
1058
  ) -> GraphOrchestrator:
1059
  """
1060
  Factory function to create a graph orchestrator.
 
1066
  use_graph: Whether to use graph execution (True) or agent chains (False)
1067
  search_handler: Optional search handler for tool execution
1068
  judge_handler: Optional judge handler for evidence assessment
1069
+ oauth_token: Optional OAuth token from HuggingFace login (takes priority over env vars)
1070
 
1071
  Returns:
1072
  Configured GraphOrchestrator instance
 
1078
  use_graph=use_graph,
1079
  search_handler=search_handler,
1080
  judge_handler=judge_handler,
1081
+ oauth_token=oauth_token,
1082
  )
src/orchestrator/planner_agent.py CHANGED
@@ -158,12 +158,13 @@ class PlannerAgent:
158
  )
159
 
160
 
161
- def create_planner_agent(model: Any | None = None) -> PlannerAgent:
162
  """
163
  Factory function to create a planner agent.
164
 
165
  Args:
166
  model: Optional Pydantic AI model. If None, uses settings default.
 
167
 
168
  Returns:
169
  Configured PlannerAgent instance
@@ -174,7 +175,7 @@ def create_planner_agent(model: Any | None = None) -> PlannerAgent:
174
  try:
175
  # Get model from settings if not provided
176
  if model is None:
177
- model = get_model()
178
 
179
  # Create and return planner agent
180
  return PlannerAgent(model=model)
 
158
  )
159
 
160
 
161
+ def create_planner_agent(model: Any | None = None, oauth_token: str | None = None) -> PlannerAgent:
162
  """
163
  Factory function to create a planner agent.
164
 
165
  Args:
166
  model: Optional Pydantic AI model. If None, uses settings default.
167
+ oauth_token: Optional OAuth token from HuggingFace login (takes priority over env vars)
168
 
169
  Returns:
170
  Configured PlannerAgent instance
 
175
  try:
176
  # Get model from settings if not provided
177
  if model is None:
178
+ model = get_model(oauth_token=oauth_token)
179
 
180
  # Create and return planner agent
181
  return PlannerAgent(model=model)
src/orchestrator/research_flow.py CHANGED
@@ -60,6 +60,7 @@ class IterativeResearchFlow:
60
  verbose: bool = True,
61
  use_graph: bool = False,
62
  judge_handler: Any | None = None,
 
63
  ) -> None:
64
  """
65
  Initialize iterative research flow.
@@ -69,19 +70,21 @@ class IterativeResearchFlow:
69
  max_time_minutes: Maximum time in minutes
70
  verbose: Whether to log progress
71
  use_graph: Whether to use graph-based execution (True) or agent chains (False)
 
72
  """
73
  self.max_iterations = max_iterations
74
  self.max_time_minutes = max_time_minutes
75
  self.verbose = verbose
76
  self.use_graph = use_graph
 
77
  self.logger = logger
78
 
79
  # Initialize agents (only needed for agent chain execution)
80
  if not use_graph:
81
- self.knowledge_gap_agent = create_knowledge_gap_agent()
82
- self.tool_selector_agent = create_tool_selector_agent()
83
- self.thinking_agent = create_thinking_agent()
84
- self.writer_agent = create_writer_agent()
85
  # Initialize judge handler (use provided or create new)
86
  self.judge_handler = judge_handler or create_judge_handler()
87
 
@@ -678,6 +681,7 @@ class DeepResearchFlow:
678
  verbose: bool = True,
679
  use_long_writer: bool = True,
680
  use_graph: bool = False,
 
681
  ) -> None:
682
  """
683
  Initialize deep research flow.
@@ -688,19 +692,21 @@ class DeepResearchFlow:
688
  verbose: Whether to log progress
689
  use_long_writer: Whether to use long writer (True) or proofreader (False)
690
  use_graph: Whether to use graph-based execution (True) or agent chains (False)
 
691
  """
692
  self.max_iterations = max_iterations
693
  self.max_time_minutes = max_time_minutes
694
  self.verbose = verbose
695
  self.use_long_writer = use_long_writer
696
  self.use_graph = use_graph
 
697
  self.logger = logger
698
 
699
  # Initialize agents (only needed for agent chain execution)
700
  if not use_graph:
701
- self.planner_agent = create_planner_agent()
702
- self.long_writer_agent = create_long_writer_agent()
703
- self.proofreader_agent = create_proofreader_agent()
704
  # Initialize judge handler for section loop completion
705
  self.judge_handler = create_judge_handler()
706
  # Initialize budget tracker for token tracking
 
60
  verbose: bool = True,
61
  use_graph: bool = False,
62
  judge_handler: Any | None = None,
63
+ oauth_token: str | None = None,
64
  ) -> None:
65
  """
66
  Initialize iterative research flow.
 
70
  max_time_minutes: Maximum time in minutes
71
  verbose: Whether to log progress
72
  use_graph: Whether to use graph-based execution (True) or agent chains (False)
73
+ oauth_token: Optional OAuth token from HuggingFace login (takes priority over env vars)
74
  """
75
  self.max_iterations = max_iterations
76
  self.max_time_minutes = max_time_minutes
77
  self.verbose = verbose
78
  self.use_graph = use_graph
79
+ self.oauth_token = oauth_token
80
  self.logger = logger
81
 
82
  # Initialize agents (only needed for agent chain execution)
83
  if not use_graph:
84
+ self.knowledge_gap_agent = create_knowledge_gap_agent(oauth_token=self.oauth_token)
85
+ self.tool_selector_agent = create_tool_selector_agent(oauth_token=self.oauth_token)
86
+ self.thinking_agent = create_thinking_agent(oauth_token=self.oauth_token)
87
+ self.writer_agent = create_writer_agent(oauth_token=self.oauth_token)
88
  # Initialize judge handler (use provided or create new)
89
  self.judge_handler = judge_handler or create_judge_handler()
90
 
 
681
  verbose: bool = True,
682
  use_long_writer: bool = True,
683
  use_graph: bool = False,
684
+ oauth_token: str | None = None,
685
  ) -> None:
686
  """
687
  Initialize deep research flow.
 
692
  verbose: Whether to log progress
693
  use_long_writer: Whether to use long writer (True) or proofreader (False)
694
  use_graph: Whether to use graph-based execution (True) or agent chains (False)
695
+ oauth_token: Optional OAuth token from HuggingFace login (takes priority over env vars)
696
  """
697
  self.max_iterations = max_iterations
698
  self.max_time_minutes = max_time_minutes
699
  self.verbose = verbose
700
  self.use_long_writer = use_long_writer
701
  self.use_graph = use_graph
702
+ self.oauth_token = oauth_token
703
  self.logger = logger
704
 
705
  # Initialize agents (only needed for agent chain execution)
706
  if not use_graph:
707
+ self.planner_agent = create_planner_agent(oauth_token=self.oauth_token)
708
+ self.long_writer_agent = create_long_writer_agent(oauth_token=self.oauth_token)
709
+ self.proofreader_agent = create_proofreader_agent(oauth_token=self.oauth_token)
710
  # Initialize judge handler for section loop completion
711
  self.judge_handler = create_judge_handler()
712
  # Initialize budget tracker for token tracking
src/orchestrator_factory.py CHANGED
@@ -46,6 +46,7 @@ def create_orchestrator(
46
  judge_handler: JudgeHandlerProtocol | None = None,
47
  config: OrchestratorConfig | None = None,
48
  mode: Literal["simple", "magentic", "advanced", "iterative", "deep", "auto"] | None = None,
 
49
  ) -> Any:
50
  """
51
  Create an orchestrator instance.
@@ -60,6 +61,7 @@ def create_orchestrator(
60
  - "iterative": Knowledge-gap-driven research (Free Tier)
61
  - "deep": Parallel section-based research (Free Tier)
62
  - "auto": Intelligent mode detection (Free Tier)
 
63
 
64
  Returns:
65
  Orchestrator instance
@@ -83,6 +85,7 @@ def create_orchestrator(
83
  use_graph=True,
84
  search_handler=search_handler,
85
  judge_handler=judge_handler,
 
86
  )
87
 
88
  # Simple mode requires handlers
 
46
  judge_handler: JudgeHandlerProtocol | None = None,
47
  config: OrchestratorConfig | None = None,
48
  mode: Literal["simple", "magentic", "advanced", "iterative", "deep", "auto"] | None = None,
49
+ oauth_token: str | None = None,
50
  ) -> Any:
51
  """
52
  Create an orchestrator instance.
 
61
  - "iterative": Knowledge-gap-driven research (Free Tier)
62
  - "deep": Parallel section-based research (Free Tier)
63
  - "auto": Intelligent mode detection (Free Tier)
64
+ oauth_token: Optional OAuth token from HuggingFace login (takes priority over env vars)
65
 
66
  Returns:
67
  Orchestrator instance
 
85
  use_graph=True,
86
  search_handler=search_handler,
87
  judge_handler=judge_handler,
88
+ oauth_token=oauth_token,
89
  )
90
 
91
  # Simple mode requires handlers
src/services/image_ocr.py CHANGED
@@ -55,10 +55,11 @@ class ImageOCRService:
55
  if self.client is None:
56
  loop = asyncio.get_running_loop()
57
  # Pass token to Client for authenticated Spaces
 
58
  if token:
59
  self.client = await loop.run_in_executor(
60
  None,
61
- lambda: Client(self.api_url, hf_token=token),
62
  )
63
  else:
64
  self.client = await loop.run_in_executor(
@@ -240,3 +241,4 @@ def get_image_ocr_service() -> ImageOCRService:
240
 
241
 
242
 
 
 
55
  if self.client is None:
56
  loop = asyncio.get_running_loop()
57
  # Pass token to Client for authenticated Spaces
58
+ # Gradio Client uses 'token' parameter, not 'hf_token'
59
  if token:
60
  self.client = await loop.run_in_executor(
61
  None,
62
+ lambda: Client(self.api_url, token=token),
63
  )
64
  else:
65
  self.client = await loop.run_in_executor(
 
241
 
242
 
243
 
244
+
src/services/multimodal_processing.py CHANGED
@@ -134,3 +134,4 @@ def get_multimodal_service() -> MultimodalService:
134
 
135
 
136
 
 
 
134
 
135
 
136
 
137
+
src/services/stt_gradio.py CHANGED
@@ -54,10 +54,11 @@ class STTService:
54
  if self.client is None:
55
  loop = asyncio.get_running_loop()
56
  # Pass token to Client for authenticated Spaces
 
57
  if token:
58
  self.client = await loop.run_in_executor(
59
  None,
60
- lambda: Client(self.api_url, hf_token=token),
61
  )
62
  else:
63
  self.client = await loop.run_in_executor(
 
54
  if self.client is None:
55
  loop = asyncio.get_running_loop()
56
  # Pass token to Client for authenticated Spaces
57
+ # Gradio Client uses 'token' parameter, not 'hf_token'
58
  if token:
59
  self.client = await loop.run_in_executor(
60
  None,
61
+ lambda: Client(self.api_url, token=token),
62
  )
63
  else:
64
  self.client = await loop.run_in_executor(
src/tools/crawl_adapter.py CHANGED
@@ -62,3 +62,4 @@ async def crawl_website(starting_url: str) -> str:
62
 
63
 
64
 
 
 
62
 
63
 
64
 
65
+
src/tools/web_search_adapter.py CHANGED
@@ -67,3 +67,4 @@ async def web_search(query: str) -> str:
67
 
68
 
69
 
 
 
67
 
68
 
69
 
70
+
src/utils/config.py CHANGED
@@ -24,7 +24,7 @@ class Settings(BaseSettings):
24
  openai_api_key: str | None = Field(default=None, description="OpenAI API key")
25
  anthropic_api_key: str | None = Field(default=None, description="Anthropic API key")
26
  llm_provider: Literal["openai", "anthropic", "huggingface"] = Field(
27
- default="openai", description="Which LLM provider to use"
28
  )
29
  openai_model: str = Field(default="gpt-5.1", description="OpenAI model name")
30
  anthropic_model: str = Field(
@@ -140,62 +140,6 @@ class Settings(BaseSettings):
140
  description="Automatically ingest evidence into RAG",
141
  )
142
 
143
- # Audio Processing Configuration
144
- tts_model: str = Field(
145
- default="hexgrad/Kokoro-82M",
146
- description="Kokoro TTS model ID for text-to-speech",
147
- )
148
- tts_voice: str = Field(
149
- default="af_heart",
150
- description="Kokoro voice ID (e.g., af_heart, af_bella, am_michael)",
151
- )
152
- tts_speed: float = Field(
153
- default=1.0,
154
- ge=0.5,
155
- le=2.0,
156
- description="TTS speech speed multiplier",
157
- )
158
- tts_gpu: str | None = Field(
159
- default="T4",
160
- description="Modal GPU type for TTS (T4, A10, A100, etc.)",
161
- )
162
- tts_timeout: int = Field(
163
- default=60,
164
- ge=10,
165
- le=300,
166
- description="TTS synthesis timeout in seconds",
167
- )
168
- stt_api_url: str = Field(
169
- default="nvidia/canary-1b-v2",
170
- description="Gradio Space URL for STT API (nvidia/canary-1b-v2)",
171
- )
172
- stt_source_lang: str = Field(
173
- default="English",
174
- description="Source language for STT transcription",
175
- )
176
- stt_target_lang: str = Field(
177
- default="English",
178
- description="Target language for STT transcription",
179
- )
180
- enable_audio_input: bool = Field(
181
- default=True,
182
- description="Enable audio input (microphone/file upload)",
183
- )
184
- enable_audio_output: bool = Field(
185
- default=True,
186
- description="Enable audio output (TTS response)",
187
- )
188
-
189
- # Image OCR Configuration
190
- ocr_api_url: str = Field(
191
- default="prithivMLmods/Multimodal-OCR3",
192
- description="Gradio Space URL for image OCR API",
193
- )
194
- enable_image_input: bool = Field(
195
- default=True,
196
- description="Enable image input (file upload with OCR)",
197
- )
198
-
199
  @property
200
  def modal_available(self) -> bool:
201
  """Check if Modal credentials are configured."""
@@ -259,16 +203,6 @@ class Settings(BaseSettings):
259
  return bool(self.tavily_api_key)
260
  return False
261
 
262
- @property
263
- def audio_available(self) -> bool:
264
- """Check if audio processing is available (Modal + STT API)."""
265
- return self.modal_available and bool(self.stt_api_url)
266
-
267
- @property
268
- def image_ocr_available(self) -> bool:
269
- """Check if image OCR is available (OCR API URL configured)."""
270
- return bool(self.ocr_api_url)
271
-
272
 
273
  def get_settings() -> Settings:
274
  """Factory function to get settings (allows mocking in tests)."""
 
24
  openai_api_key: str | None = Field(default=None, description="OpenAI API key")
25
  anthropic_api_key: str | None = Field(default=None, description="Anthropic API key")
26
  llm_provider: Literal["openai", "anthropic", "huggingface"] = Field(
27
+ default="huggingface", description="Which LLM provider to use"
28
  )
29
  openai_model: str = Field(default="gpt-5.1", description="OpenAI model name")
30
  anthropic_model: str = Field(
 
140
  description="Automatically ingest evidence into RAG",
141
  )
142
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
  @property
144
  def modal_available(self) -> bool:
145
  """Check if Modal credentials are configured."""
 
203
  return bool(self.tavily_api_key)
204
  return False
205
 
 
 
 
 
 
 
 
 
 
 
206
 
207
  def get_settings() -> Settings:
208
  """Factory function to get settings (allows mocking in tests)."""
tests/unit/middleware/__init__.py CHANGED
@@ -18,4 +18,5 @@
18
 
19
 
20
 
 
21
 
 
18
 
19
 
20
 
21
+
22
 
tests/unit/middleware/test_budget_tracker_phase7.py CHANGED
@@ -176,4 +176,5 @@ class TestIterationTokenTracking:
176
 
177
 
178
 
 
179
 
 
176
 
177
 
178
 
179
+
180
 
tests/unit/middleware/test_state_machine.py CHANGED
@@ -373,4 +373,5 @@ class TestContextVarIsolation:
373
 
374
 
375
 
 
376
 
 
373
 
374
 
375
 
376
+
377