diff options
Diffstat (limited to 'backend')
| -rw-r--r-- | backend/app/main.py | 34 | ||||
| -rw-r--r-- | backend/app/schemas.py | 9 | ||||
| -rw-r--r-- | backend/app/services/council.py | 9 |
3 files changed, 40 insertions, 12 deletions
diff --git a/backend/app/main.py b/backend/app/main.py index 9370a32..304c74f 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -472,12 +472,13 @@ async def run_council_stream( openrouter_key = get_user_api_key(resolved, "openrouter") - # Build LLMConfig + attachments for each council member + # Build LLMConfig + attachments + per-member contexts for each council member member_configs: list[LLMConfig] = [] attachments_per_model: list[list[dict] | None] = [] tools_per_model: list[list[dict] | None] = [] + contexts_per_model: list[Context | None] = [] - all_model_names = [m.model_name for m in request.council_models] + [request.chairman_model] + all_model_names = [m.model_name for m in request.council_models] + [request.chairman_model.model_name] for member in request.council_models: provider = resolve_provider(member.model_name) @@ -487,10 +488,11 @@ async def run_council_stream( config = LLMConfig( provider=provider, model_name=member.model_name, - temperature=request.temperature, + temperature=member.temperature if member.temperature is not None else request.temperature, system_prompt=request.system_prompt, api_key=api_key, - reasoning_effort=request.reasoning_effort, + reasoning_effort=member.reasoning_effort if member.reasoning_effort is not None else request.reasoning_effort, + enable_google_search=member.enable_google_search if member.enable_google_search is not None else request.enable_google_search, ) member_configs.append(config) @@ -539,16 +541,31 @@ async def run_council_stream( attachments_per_model.append(attachments or None) tools_per_model.append(tools or None) + # Per-member context override + if member.incoming_contexts: + raw = [] + for ctx in member.incoming_contexts: + raw.extend(ctx.messages) + if request.merge_strategy == MergeStrategy.SMART: + merged = smart_merge_messages(raw) + else: + merged = raw + contexts_per_model.append(Context(messages=merged)) + else: + contexts_per_model.append(None) # Use shared context + # Build chairman config - chairman_provider = resolve_provider(request.chairman_model) + chairman = request.chairman_model + chairman_provider = resolve_provider(chairman.model_name) chairman_api_key = get_user_api_key(resolved, chairman_provider.value) chairman_config = LLMConfig( provider=chairman_provider, - model_name=request.chairman_model, - temperature=request.temperature, + model_name=chairman.model_name, + temperature=chairman.temperature if chairman.temperature is not None else request.temperature, system_prompt=request.system_prompt, api_key=chairman_api_key, - reasoning_effort=request.reasoning_effort, + reasoning_effort=chairman.reasoning_effort if chairman.reasoning_effort is not None else request.reasoning_effort, + enable_google_search=chairman.enable_google_search if chairman.enable_google_search is not None else request.enable_google_search, ) return StreamingResponse( @@ -561,6 +578,7 @@ async def run_council_stream( tools_per_model=tools_per_model, openrouter_api_key=openrouter_key, images=images, + contexts_per_model=contexts_per_model, ), media_type="text/event-stream", ) diff --git a/backend/app/schemas.py b/backend/app/schemas.py index a527004..4213f15 100644 --- a/backend/app/schemas.py +++ b/backend/app/schemas.py @@ -57,16 +57,21 @@ class NodeRunRequest(BaseModel): class CouncilMemberConfig(BaseModel): model_name: str # e.g. "gpt-5", "claude-opus-4-6", "gemini-3-pro-preview" + temperature: Optional[float] = None # None = use request default + reasoning_effort: Optional[ReasoningEffort] = None + enable_google_search: Optional[bool] = None + incoming_contexts: Optional[List[Context]] = None # Per-member context override class CouncilRunRequest(BaseModel): node_id: str - incoming_contexts: List[Context] = [] + incoming_contexts: List[Context] = [] # Default context for all members user_prompt: str council_models: List[CouncilMemberConfig] # 2-6 models - chairman_model: str # Model name for synthesis + chairman_model: CouncilMemberConfig # Model config for synthesis system_prompt: Optional[str] = None temperature: float = 0.7 reasoning_effort: ReasoningEffort = ReasoningEffort.MEDIUM + enable_google_search: bool = False merge_strategy: MergeStrategy = MergeStrategy.SMART attached_file_ids: List[str] = Field(default_factory=list) scopes: List[str] = Field(default_factory=list) diff --git a/backend/app/services/council.py b/backend/app/services/council.py index d177f44..ef6fa89 100644 --- a/backend/app/services/council.py +++ b/backend/app/services/council.py @@ -21,18 +21,20 @@ async def stage1_collect_responses( tools_per_model: Optional[List[Optional[List[Dict[str, Any]]]]] = None, openrouter_api_key: Optional[str] = None, images: Optional[List[Dict[str, Any]]] = None, + contexts_per_model: Optional[List[Optional[Context]]] = None, ) -> AsyncGenerator[Dict[str, Any], None]: """ Stage 1: Query all council member models in parallel. Yields events as each model completes. - Returns final list via stage1_complete event. + If contexts_per_model is provided, each member uses its own context (None = shared context). """ async def _query_one(idx: int, config: LLMConfig) -> Dict[str, Any]: atts = attachments_per_model[idx] if attachments_per_model else None tls = tools_per_model[idx] if tools_per_model else None + member_context = (contexts_per_model[idx] if contexts_per_model and idx < len(contexts_per_model) and contexts_per_model[idx] is not None else context) try: response = await query_model_full( - context, user_prompt, config, + member_context, user_prompt, config, attachments=atts, tools=tls, openrouter_api_key=openrouter_api_key, images=images, ) @@ -255,9 +257,11 @@ async def council_event_stream( tools_per_model: Optional[List[Optional[List[Dict[str, Any]]]]] = None, openrouter_api_key: Optional[str] = None, images: Optional[List[Dict[str, Any]]] = None, + contexts_per_model: Optional[List[Optional[Context]]] = None, ) -> AsyncGenerator[str, None]: """ Master orchestrator yielding SSE JSON events through the 3-stage council process. + If contexts_per_model is provided, each member uses its own context (None entries = shared context). """ # === Stage 1 === yield _sse_event({"type": "stage1_start"}) @@ -269,6 +273,7 @@ async def council_event_stream( tools_per_model=tools_per_model, openrouter_api_key=openrouter_api_key, images=images, + contexts_per_model=contexts_per_model, ): stage1_results.append(result) yield _sse_event({ |
