Claude 24f816b6a3
Consolidate 22 sibling repos into layered organism structure
Place useful parts of the surrounding repos into sica-fondt by layer, per the
body model (Ada = membrane; brain/endocrine/capabilities/knowledge non-Ada):

- brain/        LLM reasoning + providers (dapr, hermes, MoMoA)
- capabilities/ REPRAG sidecars: hermes tools/skills, dapr tools, parallel
                dispatch, A51 channels, and the OSINT cluster
- knowledge/    LORAG corpus: 754 cyber-skills, agency personas, secure-coding,
                MITRE ATT&CK data
- reference/    defensive threat-reference (C3, shhbruh doc) + AdaYaml parser

License handling: AGPL sources (worldosint, advanced_evolution, mercury,
Reticulum) and GPL DeTTECT are SPEC-only clean-room/port descriptions — no
copyleft code copied. MIT/Apache/data parts copied as working trees.

Safety: shhbruh escape/persistence material and C3 covert-C2 kept as reference
only, not wired into the running organism. See CONSOLIDATION.md.

https://claude.ai/code/session_01UehUqEXXJJCsHoA4voCU5c
2026-06-10 06:53:01 +00:00

218 lines
7.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#
# Copyright 2026 The Dapr Authors
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import logging
from pathlib import Path
from typing import (
Any,
ClassVar,
Dict,
Iterable,
Iterator,
List,
Literal,
Optional,
Type,
Union,
)
from pydantic import BaseModel, Field
from dapr_agents.llm.chat import ChatClientBase
from dapr_agents.llm.huggingface.client import HFHubInferenceClientBase
from dapr_agents.llm.utils import RequestHandler, ResponseHandler
from dapr_agents.prompt.base import PromptTemplateBase
from dapr_agents.prompt.prompty import Prompty
from dapr_agents.tool import AgentTool
from dapr_agents.types.message import (
BaseMessage,
LLMChatCandidateChunk,
LLMChatResponse,
)
logger = logging.getLogger(__name__)
class HFHubChatClient(HFHubInferenceClientBase, ChatClientBase):
"""
Chat client for Hugging Face Hub's Inference API.
Extends:
- HFHubInferenceClientBase: manages HF-specific auth, endpoints, retries.
- ChatClientBase: provides the `.from_prompty()` and `.generate()` contract.
Supports only function_call-style structured responses.
"""
prompty: Optional[Prompty] = Field(
default=None, description="Optional Prompty instance for templating."
)
prompt_template: Optional[PromptTemplateBase] = Field(
default=None, description="Optional prompt-template to format inputs."
)
SUPPORTED_STRUCTURED_MODES: ClassVar[set[str]] = {"function_call"}
def model_post_init(self, __context: Any) -> None:
"""
After Pydantic __init__, set the internal API type to "chat".
"""
self._api = "chat"
super().model_post_init(__context)
@classmethod
def from_prompty(
cls,
prompty_source: Union[str, Path],
timeout: Union[int, float, Dict[str, Any]] = 1500,
) -> "HFHubChatClient":
"""
Load a Prompty spec (file or inline), extract model config & prompt template,
and return a configured HFHubChatClient.
Args:
prompty_source: Path or inline text of a Prompty YAML/JSON.
timeout: Request timeout (seconds or HTTPX timeout dict).
Returns:
HFHubChatClient: client ready for .generate() calls.
"""
prompty_instance = Prompty.load(prompty_source)
prompt_template = Prompty.to_prompt_template(prompty_instance)
cfg = prompty_instance.model.configuration
return cls.model_validate(
{
"model": cfg.name,
"api_key": cfg.api_key,
"base_url": cfg.base_url,
"headers": cfg.headers,
"cookies": cfg.cookies,
"proxies": cfg.proxies,
"timeout": timeout,
"prompty": prompty_instance,
"prompt_template": prompt_template,
}
)
def generate(
self,
messages: Union[
str,
Dict[str, Any],
BaseMessage,
Iterable[Union[Dict[str, Any], BaseMessage]],
] = None,
*,
input_data: Optional[Dict[str, Any]] = None,
model: Optional[str] = None,
tools: Optional[List[Union[AgentTool, Dict[str, Any]]]] = None,
response_format: Optional[Type[BaseModel]] = None,
structured_mode: Literal["function_call"] = "function_call",
stream: bool = False,
**kwargs: Any, # accept any extra params, even if unused
) -> Union[
Iterator[LLMChatCandidateChunk],
LLMChatResponse,
BaseModel,
List[BaseModel],
]:
"""
Issue a chat completion via Hugging Face's Inference API.
- If `stream=True` in **kwargs**, returns an iterator of `LLMChatCandidateChunk`.
- Otherwise returns either:
• raw `AssistantMessage` wrapped in `LLMChatResponse`, or
• validated Pydantic model(s) per `response_format`.
Args:
messages: Pre-built messages or None to use `input_data`.
input_data: Variables for the Prompty template.
model: Override the client's default model name.
tools: List of AgentTool or dict specs.
response_format: Pydantic model (or List[Model]) for structured output.
structured_mode: Must be `"function_call"` (only supported mode here).
stream: If True, return an iterator of `LLMChatCandidateChunk`.
**kwargs: Any other LLM params (temperature, top_p, stream, etc.).
Returns:
• `Iterator[LLMChatCandidateChunk]` if streaming
• `LLMChatResponse` or Pydantic instance(s) if non-streaming
Raises:
ValueError: on invalid `structured_mode`, missing prompts, or API errors.
"""
# 1) Validate structured_mode
if structured_mode not in self.SUPPORTED_STRUCTURED_MODES:
raise ValueError(
f"structured_mode must be one of {self.SUPPORTED_STRUCTURED_MODES}"
)
# 2) If using a prompt template, build messages
if input_data:
if not self.prompt_template:
raise ValueError("No prompt_template set for input_data usage.")
logger.info("Formatting messages via prompt_template.")
messages = self.prompt_template.format_prompt(**input_data)
if not messages:
raise ValueError("Either messages or input_data must be provided.")
# 3) Normalize messages + merge client/prompty defaults
params = {"messages": RequestHandler.normalize_chat_messages(messages)}
if self.prompty:
params = {**self.prompty.model.parameters.model_dump(), **params, **kwargs}
else:
params.update(kwargs)
# 4) Add the stream parameter explicitly to params
params["stream"] = stream
# 5) Override model if given
params["model"] = model or self.model
# 6) Inject tools / response_format via RequestHandler
params = RequestHandler.process_params(
params,
llm_provider=self.provider,
tools=tools,
response_format=response_format,
structured_mode=structured_mode,
)
# 7) Call HF API + delegate parsing to ResponseHandler
try:
logger.info("Calling HF ChatCompletion Inference API...")
logger.debug(f"HF params: {params}")
response = self.client.chat.completions.create(**params)
logger.info("HF ChatCompletion response received.")
# HF-specific errorcode handling
code = getattr(response, "code", 200)
if code != 200:
msg = getattr(response, "message", response)
logger.error(f"❌ HF error {code}: {msg}")
raise RuntimeError(f"HuggingFace error {code}: {msg}")
return ResponseHandler.process_response(
response=response,
llm_provider=self.provider,
response_format=response_format,
structured_mode=structured_mode,
stream=stream,
)
except Exception as e:
logger.error("Hugging Face ChatCompletion API error", exc_info=True)
raise ValueError(f"Failed to process HF chat completion: {e}") from e