Siggmoid Cursor commited on
Commit
51e25cb
·
1 Parent(s): d2b7a80

Fix AI feedback: use HF Inference chat API via LangChain

Browse files
Files changed (3) hide show
  1. requirements.txt +0 -1
  2. services/feedback.py +20 -9
  3. services/llm.py +103 -25
requirements.txt CHANGED
@@ -8,4 +8,3 @@ pydantic
8
  python-multipart
9
  langchain
10
  langchain-core
11
- langchain-huggingface
 
8
  python-multipart
9
  langchain
10
  langchain-core
 
services/feedback.py CHANGED
@@ -1,6 +1,10 @@
 
 
1
  from langchain_core.messages import HumanMessage, SystemMessage
2
 
3
- from .llm import get_llm
 
 
4
 
5
  SYSTEM_PROMPT = """You are an ATS resume analyst.
6
 
@@ -33,16 +37,23 @@ Skill Overlap: {gaps['skill_overlap_percentage']}%
33
  Provide the 3-section analysis now."""
34
 
35
  try:
36
- response = get_llm().invoke(
 
37
  [
38
  SystemMessage(content=SYSTEM_PROMPT),
39
  HumanMessage(content=user_prompt),
40
  ]
41
  )
42
- content = response.content
43
- if isinstance(content, str) and content.strip():
44
- return content.strip()
45
- except Exception:
46
- pass
47
-
48
- return "Feedback generation failed."
 
 
 
 
 
 
 
1
+ import logging
2
+
3
  from langchain_core.messages import HumanMessage, SystemMessage
4
 
5
+ from .llm import get_hf_token, invoke_chat
6
+
7
+ logger = logging.getLogger(__name__)
8
 
9
  SYSTEM_PROMPT = """You are an ATS resume analyst.
10
 
 
37
  Provide the 3-section analysis now."""
38
 
39
  try:
40
+ get_hf_token()
41
+ return invoke_chat(
42
  [
43
  SystemMessage(content=SYSTEM_PROMPT),
44
  HumanMessage(content=user_prompt),
45
  ]
46
  )
47
+ except ValueError as exc:
48
+ logger.error("HF_TOKEN missing: %s", exc)
49
+ return (
50
+ "AI feedback unavailable: HF_TOKEN is not configured. "
51
+ "Add your Hugging Face token under Space Settings → Repository secrets."
52
+ )
53
+ except Exception as exc:
54
+ logger.exception("Feedback generation failed")
55
+ return (
56
+ "AI feedback could not be generated. "
57
+ "Check that HF_TOKEN has Inference access and the Space logs for details. "
58
+ f"({type(exc).__name__})"
59
+ )
services/llm.py CHANGED
@@ -1,47 +1,125 @@
 
1
  import os
 
 
2
 
3
- from langchain_core.messages import HumanMessage, SystemMessage
4
- from langchain_huggingface import ChatHuggingFace
 
 
 
 
 
5
 
6
  MODEL_ID = "allenai/Olmo-3-7B-Instruct"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
 
8
- _chat_model: ChatHuggingFace | None = None
 
9
 
 
 
 
10
 
11
- def get_llm() -> ChatHuggingFace:
12
- """Return a shared ChatHuggingFace client (Hugging Face Inference API via LangChain)."""
13
- global _chat_model
14
- if _chat_model is None:
15
- token = (
16
- os.environ.get("HF_TOKEN")
17
- or os.environ.get("HUGGINGFACEHUB_API_TOKEN")
18
- or os.environ.get("HUGGING_FACE_HUB_TOKEN")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  )
20
- if not token:
21
- raise ValueError(
22
- "HF_TOKEN is not set. Add it as a Space secret or in your local .env file."
23
- )
24
- _chat_model = ChatHuggingFace(
25
- model=MODEL_ID,
26
- token=token,
27
- temperature=0.2,
28
- max_tokens=512,
29
  )
30
- return _chat_model
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
 
32
 
33
  if __name__ == "__main__":
34
- llm = get_llm()
35
- response = llm.invoke(
36
  [
37
  SystemMessage(content="You are an ATS resume analyst."),
38
  HumanMessage(
39
  content=(
40
  "ATS Scores: Semantic 0.45, Keyword 0.70, Final 0.68. "
41
- "Missing: api, tensorflow, docker. Skill overlap: 70%. "
42
  "Write 3 short sections: Score Explanation, Weak Areas, Actionable Improvements."
43
  )
44
  ),
45
  ]
46
  )
47
- print(response.content)
 
1
+ import logging
2
  import os
3
+ from functools import lru_cache
4
+ from typing import Any, List, Optional
5
 
6
+ from huggingface_hub import InferenceClient
7
+ from langchain_core.language_models.chat_models import BaseChatModel
8
+ from langchain_core.messages import AIMessage, BaseMessage, HumanMessage, SystemMessage
9
+ from langchain_core.outputs import ChatGeneration, ChatResult
10
+ from pydantic import Field
11
+
12
+ logger = logging.getLogger(__name__)
13
 
14
  MODEL_ID = "allenai/Olmo-3-7B-Instruct"
15
+ FALLBACK_MODEL_ID = "HuggingFaceH4/zephyr-7b-beta"
16
+
17
+
18
+ def get_hf_token() -> str:
19
+ token = (
20
+ os.environ.get("HF_TOKEN")
21
+ or os.environ.get("HUGGINGFACEHUB_API_TOKEN")
22
+ or os.environ.get("HUGGING_FACE_HUB_TOKEN")
23
+ )
24
+ if not token:
25
+ raise ValueError(
26
+ "HF_TOKEN is not set. Add it as a Space secret (Settings → Repository secrets)."
27
+ )
28
+ # LangChain / huggingface_hub also read this name
29
+ os.environ.setdefault("HUGGINGFACEHUB_API_TOKEN", token)
30
+ return token
31
+
32
+
33
+ @lru_cache(maxsize=1)
34
+ def get_inference_client() -> InferenceClient:
35
+ return InferenceClient(api_key=get_hf_token())
36
+
37
 
38
+ class HuggingFaceInferenceChat(BaseChatModel):
39
+ """LangChain chat model using Hugging Face Inference API chat.completions."""
40
 
41
+ model_id: str = Field(default=MODEL_ID)
42
+ max_tokens: int = 512
43
+ temperature: float = 0.2
44
 
45
+ @property
46
+ def _llm_type(self) -> str:
47
+ return "huggingface-inference-chat"
48
+
49
+ def _to_hf_messages(self, messages: List[BaseMessage]) -> list[dict[str, str]]:
50
+ hf_messages: list[dict[str, str]] = []
51
+ for msg in messages:
52
+ if isinstance(msg, SystemMessage):
53
+ hf_messages.append({"role": "system", "content": str(msg.content)})
54
+ elif isinstance(msg, HumanMessage):
55
+ hf_messages.append({"role": "user", "content": str(msg.content)})
56
+ elif isinstance(msg, AIMessage):
57
+ hf_messages.append({"role": "assistant", "content": str(msg.content)})
58
+ return hf_messages
59
+
60
+ def _generate(
61
+ self,
62
+ messages: List[BaseMessage],
63
+ stop: Optional[List[str]] = None,
64
+ run_manager: Any = None,
65
+ **kwargs: Any,
66
+ ) -> ChatResult:
67
+ client = get_inference_client()
68
+ response = client.chat.completions.create(
69
+ model=self.model_id,
70
+ messages=self._to_hf_messages(messages),
71
+ max_tokens=self.max_tokens,
72
+ temperature=self.temperature,
73
  )
74
+ if not response.choices:
75
+ raise RuntimeError(f"No choices returned for model {self.model_id}")
76
+ content = response.choices[0].message.content or ""
77
+ return ChatResult(
78
+ generations=[ChatGeneration(message=AIMessage(content=content))]
 
 
 
 
79
  )
80
+
81
+
82
+ _llm: HuggingFaceInferenceChat | None = None
83
+
84
+
85
+ def get_llm(model_id: str = MODEL_ID) -> HuggingFaceInferenceChat:
86
+ global _llm
87
+ if _llm is None or _llm.model_id != model_id:
88
+ get_hf_token()
89
+ _llm = HuggingFaceInferenceChat(model_id=model_id)
90
+ return _llm
91
+
92
+
93
+ def invoke_chat(messages: List[BaseMessage], model_id: str = MODEL_ID) -> str:
94
+ """Call primary model, then fallback if the provider rejects the request."""
95
+ last_error: Exception | None = None
96
+ for mid in (model_id, FALLBACK_MODEL_ID):
97
+ try:
98
+ llm = get_llm(mid)
99
+ result = llm.invoke(messages)
100
+ text = result.content if isinstance(result.content, str) else str(result.content)
101
+ if text.strip():
102
+ return text.strip()
103
+ except Exception as exc:
104
+ last_error = exc
105
+ logger.warning("HF chat failed for model %s: %s", mid, exc)
106
+ global _llm
107
+ _llm = None
108
+ raise RuntimeError(str(last_error) if last_error else "Unknown inference error")
109
 
110
 
111
  if __name__ == "__main__":
112
+ logging.basicConfig(level=logging.INFO)
113
+ out = invoke_chat(
114
  [
115
  SystemMessage(content="You are an ATS resume analyst."),
116
  HumanMessage(
117
  content=(
118
  "ATS Scores: Semantic 0.45, Keyword 0.70, Final 0.68. "
119
+ "Missing: docker, tensorflow. Skill overlap: 70%. "
120
  "Write 3 short sections: Score Explanation, Weak Areas, Actionable Improvements."
121
  )
122
  ),
123
  ]
124
  )
125
+ print(out)