카테고리 없음
사내에서 Langchain 사용시 발생하는 SSL 오류 해결방법
aimler
2025. 10. 28. 14:16
반응형
사내에서 Langchain을 사용할 경우 아래와 같이 오류가 발생한다면 모듈 파일을 수정해서 오류를 우회할 수 있습니다.
✅ 코드
llm = ChatOpenAI(
api_key="USER API Key",
model="gpt-4o-mini"
)
@tool
def get_weather(city: str) -> str:
"""Get weather for a given city."""
return f"It's always sunny in {city}!"
agent = create_agent(
model=llm,
tools=[get_weather],
system_prompt="You are a helpful assistant",
)
# Run the agent
agent.invoke(
{"messages": [{"role": "user", "content": "what is the weather in sf"}]}
)
✅ 오류 내용
---------------------------------------------------------------------------
ConnectError Traceback (most recent call last)
File c:\Users\User\.conda\envs\Orange\Lib\site-packages\httpx\_transports\default.py:101, in map_httpcore_exceptions()
100 try:
--> 101 yield
102 except Exception as exc:
File c:\Users\User\.conda\envs\Orange\Lib\site-packages\httpx\_transports\default.py:250, in HTTPTransport.handle_request(self, request)
249 with map_httpcore_exceptions():
--> 250 resp = self._pool.handle_request(req)
252 assert isinstance(resp.stream, typing.Iterable)
File c:\Users\User\.conda\envs\Orange\Lib\site-packages\httpcore\_sync\connection_pool.py:256, in ConnectionPool.handle_request(self, request)
255 self._close_connections(closing)
--> 256 raise exc from None
258 # Return the response. Note that in this case we still have to manage
259 # the point at which the response is closed.
File c:\Users\User\.conda\envs\Orange\Lib\site-packages\httpcore\_sync\connection_pool.py:236, in ConnectionPool.handle_request(self, request)
234 try:
235 # Send the request on the assigned connection.
--> 236 response = connection.handle_request(
237 pool_request.request
238 )
239 except ConnectionNotAvailable:
240 # In some cases a connection may initially be available to
241 # handle a request, but then become unavailable.
242 #
243 # In this case we clear the connection and try again.
File c:\Users\User\.conda\envs\Orange\Lib\site-packages\httpcore\_sync\connection.py:101, in HTTPConnection.handle_request(self, request)
100 self._connect_failed = True
--> 101 raise exc
103 return self._connection.handle_request(request)
File c:\Users\User\.conda\envs\Orange\Lib\site-packages\httpcore\_sync\connection.py:78, in HTTPConnection.handle_request(self, request)
77 if self._connection is None:
---> 78 stream = self._connect(request)
80 ssl_object = stream.get_extra_info("ssl_object")
File c:\Users\User\.conda\envs\Orange\Lib\site-packages\httpcore\_sync\connection.py:156, in HTTPConnection._connect(self, request)
155 with Trace("start_tls", logger, request, kwargs) as trace:
--> 156 stream = stream.start_tls(**kwargs)
157 trace.return_value = stream
File c:\Users\User\.conda\envs\Orange\Lib\site-packages\httpcore\_backends\sync.py:154, in SyncStream.start_tls(self, ssl_context, server_hostname, timeout)
150 exc_map: ExceptionMapping = {
151 socket.timeout: ConnectTimeout,
152 OSError: ConnectError,
153 }
--> 154 with map_exceptions(exc_map):
155 try:
File c:\Users\User\.conda\envs\Orange\Lib\contextlib.py:158, in _GeneratorContextManager.__exit__(self, typ, value, traceback)
157 try:
--> 158 self.gen.throw(value)
159 except StopIteration as exc:
160 # Suppress StopIteration *unless* it's the same exception that
161 # was passed to throw(). This prevents a StopIteration
162 # raised inside the "with" statement from being suppressed.
File c:\Users\User\.conda\envs\Orange\Lib\site-packages\httpcore\_exceptions.py:14, in map_exceptions(map)
13 if isinstance(exc, from_exc):
---> 14 raise to_exc(exc) from exc
15 raise
ConnectError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1000)
The above exception was the direct cause of the following exception:
ConnectError Traceback (most recent call last)
File c:\Users\User\.conda\envs\Orange\Lib\site-packages\openai\_base_client.py:982, in SyncAPIClient.request(self, cast_to, options, stream, stream_cls)
981 try:
--> 982 response = self._client.send(
983 request,
984 stream=stream or self._should_stream_response_body(request=request),
985 **kwargs,
986 )
987 except httpx.TimeoutException as err:
File c:\Users\User\.conda\envs\Orange\Lib\site-packages\httpx\_client.py:914, in Client.send(self, request, stream, auth, follow_redirects)
912 auth = self._build_request_auth(request, auth)
--> 914 response = self._send_handling_auth(
915 request,
916 auth=auth,
917 follow_redirects=follow_redirects,
918 history=[],
919 )
920 try:
File c:\Users\User\.conda\envs\Orange\Lib\site-packages\httpx\_client.py:942, in Client._send_handling_auth(self, request, auth, follow_redirects, history)
941 while True:
--> 942 response = self._send_handling_redirects(
943 request,
944 follow_redirects=follow_redirects,
945 history=history,
946 )
947 try:
File c:\Users\User\.conda\envs\Orange\Lib\site-packages\httpx\_client.py:979, in Client._send_handling_redirects(self, request, follow_redirects, history)
977 hook(request)
--> 979 response = self._send_single_request(request)
980 try:
File c:\Users\User\.conda\envs\Orange\Lib\site-packages\httpx\_client.py:1014, in Client._send_single_request(self, request)
1013 with request_context(request=request):
-> 1014 response = transport.handle_request(request)
1016 assert isinstance(response.stream, SyncByteStream)
File c:\Users\User\.conda\envs\Orange\Lib\site-packages\httpx\_transports\default.py:249, in HTTPTransport.handle_request(self, request)
237 req = httpcore.Request(
238 method=request.method,
239 url=httpcore.URL(
(...) 247 extensions=request.extensions,
248 )
--> 249 with map_httpcore_exceptions():
250 resp = self._pool.handle_request(req)
File c:\Users\User\.conda\envs\Orange\Lib\contextlib.py:158, in _GeneratorContextManager.__exit__(self, typ, value, traceback)
157 try:
--> 158 self.gen.throw(value)
159 except StopIteration as exc:
160 # Suppress StopIteration *unless* it's the same exception that
161 # was passed to throw(). This prevents a StopIteration
162 # raised inside the "with" statement from being suppressed.
File c:\Users\User\.conda\envs\Orange\Lib\site-packages\httpx\_transports\default.py:118, in map_httpcore_exceptions()
117 message = str(exc)
--> 118 raise mapped_exc(message) from exc
ConnectError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1000)
The above exception was the direct cause of the following exception:
APIConnectionError Traceback (most recent call last)
Cell In[3], line 17
10 agent = create_agent(
11 model=llm,
12 tools=[get_weather],
13 system_prompt="You are a helpful assistant",
14 )
16 # Run the agent
---> 17 agent.invoke(
18 {"messages": [{"role": "user", "content": "what is the weather in sf"}]}
19 )
File c:\Users\User\.conda\envs\Orange\Lib\site-packages\langgraph\pregel\main.py:3094, in Pregel.invoke(self, input, config, context, stream_mode, print_mode, output_keys, interrupt_before, interrupt_after, durability, **kwargs)
3091 chunks: list[dict[str, Any] | Any] = []
3092 interrupts: list[Interrupt] = []
-> 3094 for chunk in self.stream(
3095 input,
3096 config,
3097 context=context,
3098 stream_mode=["updates", "values"]
3099 if stream_mode == "values"
3100 else stream_mode,
3101 print_mode=print_mode,
3102 output_keys=output_keys,
3103 interrupt_before=interrupt_before,
3104 interrupt_after=interrupt_after,
3105 durability=durability,
3106 **kwargs,
3107 ):
3108 if stream_mode == "values":
3109 if len(chunk) == 2:
File c:\Users\User\.conda\envs\Orange\Lib\site-packages\langgraph\pregel\main.py:2679, in Pregel.stream(self, input, config, context, stream_mode, print_mode, output_keys, interrupt_before, interrupt_after, durability, subgraphs, debug, **kwargs)
2677 for task in loop.match_cached_writes():
2678 loop.output_writes(task.id, task.writes, cached=True)
-> 2679 for _ in runner.tick(
2680 [t for t in loop.tasks.values() if not t.writes],
2681 timeout=self.step_timeout,
2682 get_waiter=get_waiter,
2683 schedule_task=loop.accept_push,
2684 ):
2685 # emit output
2686 yield from _output(
2687 stream_mode, print_mode, subgraphs, stream.get, queue.Empty
2688 )
2689 loop.after_tick()
File c:\Users\User\.conda\envs\Orange\Lib\site-packages\langgraph\pregel\_runner.py:167, in PregelRunner.tick(self, tasks, reraise, timeout, retry_policy, get_waiter, schedule_task)
165 t = tasks[0]
166 try:
--> 167 run_with_retry(
168 t,
169 retry_policy,
170 configurable={
171 CONFIG_KEY_CALL: partial(
172 _call,
173 weakref.ref(t),
174 retry_policy=retry_policy,
175 futures=weakref.ref(futures),
176 schedule_task=schedule_task,
177 submit=self.submit,
178 ),
179 },
180 )
181 self.commit(t, None)
182 except Exception as exc:
File c:\Users\User\.conda\envs\Orange\Lib\site-packages\langgraph\pregel\_retry.py:42, in run_with_retry(task, retry_policy, configurable)
40 task.writes.clear()
41 # run the task
---> 42 return task.proc.invoke(task.input, config)
43 except ParentCommand as exc:
44 ns: str = config[CONF][CONFIG_KEY_CHECKPOINT_NS]
File c:\Users\User\.conda\envs\Orange\Lib\site-packages\langgraph\_internal\_runnable.py:656, in RunnableSeq.invoke(self, input, config, **kwargs)
654 # run in context
655 with set_config_context(config, run) as context:
--> 656 input = context.run(step.invoke, input, config, **kwargs)
657 else:
658 input = step.invoke(input, config)
File c:\Users\User\.conda\envs\Orange\Lib\site-packages\langgraph\_internal\_runnable.py:400, in RunnableCallable.invoke(self, input, config, **kwargs)
398 run_manager.on_chain_end(ret)
399 else:
--> 400 ret = self.func(*args, **kwargs)
401 if self.recurse and isinstance(ret, Runnable):
402 return ret.invoke(input, config)
File c:\Users\User\.conda\envs\Orange\Lib\site-packages\langchain\agents\factory.py:1034, in create_agent.<locals>.model_node(state, runtime)
1021 request = ModelRequest(
1022 model=model,
1023 tools=default_tools,
(...) 1029 runtime=runtime,
1030 )
1032 if wrap_model_call_handler is None:
1033 # No handlers - execute directly
-> 1034 response = _execute_model_sync(request)
1035 else:
1036 # Call composed handler with base handler
1037 response = wrap_model_call_handler(request, _execute_model_sync)
File c:\Users\User\.conda\envs\Orange\Lib\site-packages\langchain\agents\factory.py:1007, in create_agent.<locals>._execute_model_sync(request)
1004 if request.system_prompt:
1005 messages = [SystemMessage(request.system_prompt), *messages]
-> 1007 output = model_.invoke(messages)
1009 # Handle model output to get messages and structured_response
1010 handled_output = _handle_model_output(output, effective_response_format)
File c:\Users\User\.conda\envs\Orange\Lib\site-packages\langchain_core\runnables\base.py:5489, in RunnableBindingBase.invoke(self, input, config, **kwargs)
5482 @override
5483 def invoke(
5484 self,
(...) 5487 **kwargs: Any | None,
5488 ) -> Output:
-> 5489 return self.bound.invoke(
5490 input,
5491 self._merge_configs(config),
5492 **{**self.kwargs, **kwargs},
5493 )
File c:\Users\User\.conda\envs\Orange\Lib\site-packages\langchain_core\language_models\chat_models.py:379, in BaseChatModel.invoke(self, input, config, stop, **kwargs)
365 @override
366 def invoke(
367 self,
(...) 372 **kwargs: Any,
373 ) -> AIMessage:
374 config = ensure_config(config)
375 return cast(
376 "AIMessage",
377 cast(
378 "ChatGeneration",
--> 379 self.generate_prompt(
380 [self._convert_input(input)],
381 stop=stop,
382 callbacks=config.get("callbacks"),
383 tags=config.get("tags"),
384 metadata=config.get("metadata"),
385 run_name=config.get("run_name"),
386 run_id=config.pop("run_id", None),
387 **kwargs,
388 ).generations[0][0],
389 ).message,
390 )
File c:\Users\User\.conda\envs\Orange\Lib\site-packages\langchain_core\language_models\chat_models.py:1088, in BaseChatModel.generate_prompt(self, prompts, stop, callbacks, **kwargs)
1079 @override
1080 def generate_prompt(
1081 self,
(...) 1085 **kwargs: Any,
1086 ) -> LLMResult:
1087 prompt_messages = [p.to_messages() for p in prompts]
-> 1088 return self.generate(prompt_messages, stop=stop, callbacks=callbacks, **kwargs)
File c:\Users\User\.conda\envs\Orange\Lib\site-packages\langchain_core\language_models\chat_models.py:903, in BaseChatModel.generate(self, messages, stop, callbacks, tags, metadata, run_name, run_id, **kwargs)
900 for i, m in enumerate(input_messages):
901 try:
902 results.append(
--> 903 self._generate_with_cache(
904 m,
905 stop=stop,
906 run_manager=run_managers[i] if run_managers else None,
907 **kwargs,
908 )
909 )
910 except BaseException as e:
911 if run_managers:
File c:\Users\User\.conda\envs\Orange\Lib\site-packages\langchain_core\language_models\chat_models.py:1192, in BaseChatModel._generate_with_cache(self, messages, stop, run_manager, **kwargs)
1190 result = generate_from_stream(iter(chunks))
1191 elif inspect.signature(self._generate).parameters.get("run_manager"):
-> 1192 result = self._generate(
1193 messages, stop=stop, run_manager=run_manager, **kwargs
1194 )
1195 else:
1196 result = self._generate(messages, stop=stop, **kwargs)
File c:\Users\User\.conda\envs\Orange\Lib\site-packages\langchain_openai\chat_models\base.py:1300, in BaseChatOpenAI._generate(self, messages, stop, run_manager, **kwargs)
1298 if raw_response is not None and hasattr(raw_response, "http_response"):
1299 e.response = raw_response.http_response # type: ignore[attr-defined]
-> 1300 raise e
1301 if (
1302 self.include_response_headers
1303 and raw_response is not None
1304 and hasattr(raw_response, "headers")
1305 ):
1306 generation_info = {"headers": dict(raw_response.headers)}
File c:\Users\User\.conda\envs\Orange\Lib\site-packages\langchain_openai\chat_models\base.py:1295, in BaseChatOpenAI._generate(self, messages, stop, run_manager, **kwargs)
1288 return _construct_lc_result_from_responses_api(
1289 response,
1290 schema=original_schema_obj,
1291 metadata=generation_info,
1292 output_version=self.output_version,
1293 )
1294 else:
-> 1295 raw_response = self.client.with_raw_response.create(**payload)
1296 response = raw_response.parse()
1297 except Exception as e:
File c:\Users\User\.conda\envs\Orange\Lib\site-packages\openai\_legacy_response.py:364, in to_raw_response_wrapper.<locals>.wrapped(*args, **kwargs)
360 extra_headers[RAW_RESPONSE_HEADER] = "true"
362 kwargs["extra_headers"] = extra_headers
--> 364 return cast(LegacyAPIResponse[R], func(*args, **kwargs))
File c:\Users\User\.conda\envs\Orange\Lib\site-packages\openai\_utils\_utils.py:286, in required_args.<locals>.inner.<locals>.wrapper(*args, **kwargs)
284 msg = f"Missing required argument: {quote(missing[0])}"
285 raise TypeError(msg)
--> 286 return func(*args, **kwargs)
File c:\Users\User\.conda\envs\Orange\Lib\site-packages\openai\resources\chat\completions\completions.py:1156, in Completions.create(self, messages, model, audio, frequency_penalty, function_call, functions, logit_bias, logprobs, max_completion_tokens, max_tokens, metadata, modalities, n, parallel_tool_calls, prediction, presence_penalty, prompt_cache_key, reasoning_effort, response_format, safety_identifier, seed, service_tier, stop, store, stream, stream_options, temperature, tool_choice, tools, top_logprobs, top_p, user, verbosity, web_search_options, extra_headers, extra_query, extra_body, timeout)
1110 @required_args(["messages", "model"], ["messages", "model", "stream"])
1111 def create(
1112 self,
(...) 1153 timeout: float | httpx.Timeout | None | NotGiven = not_given,
1154 ) -> ChatCompletion | Stream[ChatCompletionChunk]:
1155 validate_response_format(response_format)
-> 1156 return self._post(
1157 "/chat/completions",
1158 body=maybe_transform(
1159 {
1160 "messages": messages,
1161 "model": model,
1162 "audio": audio,
1163 "frequency_penalty": frequency_penalty,
1164 "function_call": function_call,
1165 "functions": functions,
1166 "logit_bias": logit_bias,
1167 "logprobs": logprobs,
1168 "max_completion_tokens": max_completion_tokens,
1169 "max_tokens": max_tokens,
1170 "metadata": metadata,
1171 "modalities": modalities,
1172 "n": n,
1173 "parallel_tool_calls": parallel_tool_calls,
1174 "prediction": prediction,
1175 "presence_penalty": presence_penalty,
1176 "prompt_cache_key": prompt_cache_key,
1177 "reasoning_effort": reasoning_effort,
1178 "response_format": response_format,
1179 "safety_identifier": safety_identifier,
1180 "seed": seed,
1181 "service_tier": service_tier,
1182 "stop": stop,
1183 "store": store,
1184 "stream": stream,
1185 "stream_options": stream_options,
1186 "temperature": temperature,
1187 "tool_choice": tool_choice,
1188 "tools": tools,
1189 "top_logprobs": top_logprobs,
1190 "top_p": top_p,
1191 "user": user,
1192 "verbosity": verbosity,
1193 "web_search_options": web_search_options,
1194 },
1195 completion_create_params.CompletionCreateParamsStreaming
1196 if stream
1197 else completion_create_params.CompletionCreateParamsNonStreaming,
1198 ),
1199 options=make_request_options(
1200 extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
1201 ),
1202 cast_to=ChatCompletion,
1203 stream=stream or False,
1204 stream_cls=Stream[ChatCompletionChunk],
1205 )
File c:\Users\User\.conda\envs\Orange\Lib\site-packages\openai\_base_client.py:1259, in SyncAPIClient.post(self, path, cast_to, body, options, files, stream, stream_cls)
1245 def post(
1246 self,
1247 path: str,
(...) 1254 stream_cls: type[_StreamT] | None = None,
1255 ) -> ResponseT | _StreamT:
1256 opts = FinalRequestOptions.construct(
1257 method="post", url=path, json_data=body, files=to_httpx_files(files), **options
1258 )
-> 1259 return cast(ResponseT, self.request(cast_to, opts, stream=stream, stream_cls=stream_cls))
File c:\Users\User\.conda\envs\Orange\Lib\site-packages\openai\_base_client.py:1014, in SyncAPIClient.request(self, cast_to, options, stream, stream_cls)
1011 continue
1013 log.debug("Raising connection error")
-> 1014 raise APIConnectionError(request=request) from err
1016 log.debug(
1017 'HTTP Response: %s %s "%i %s" %s',
1018 request.method,
(...) 1022 response.headers,
1023 )
1024 log.debug("request_id: %s", response.headers.get("x-request-id"))
APIConnectionError: Connection error.
During task with name 'model' and id '165fa95b-????-????-????-????209ef8c5'
☑️ 해결 방법
C:\Users\User\.conda\envs\Orange\Lib\site-packages\httpx\_transports\default.py
모듈이 설치된 폴더로 이동하여 default.py를 아래와 같이 수정합니다.
class HTTPTransport(BaseTransport):
def __init__(
self,
verify: ssl.SSLContext | str | bool = True,
cert: CertTypes | None = None,
trust_env: bool = True,
http1: bool = True,
http2: bool = False,
limits: Limits = DEFAULT_LIMITS,
proxy: ProxyTypes | None = None,
uds: str | None = None,
local_address: str | None = None,
retries: int = 0,
socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
) -> None:
import httpcore
verify=False # TODO 강제 False설정
proxy = Proxy(url=proxy) if isinstance(proxy, (str, URL)) else proxy
ssl_context = create_ssl_context(verify=verify, cert=cert, trust_env=trust_env)
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 생략 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
class AsyncHTTPTransport(AsyncBaseTransport):
def __init__(
self,
verify: ssl.SSLContext | str | bool = True,
cert: CertTypes | None = None,
trust_env: bool = True,
http1: bool = True,
http2: bool = False,
limits: Limits = DEFAULT_LIMITS,
proxy: ProxyTypes | None = None,
uds: str | None = None,
local_address: str | None = None,
retries: int = 0,
socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
) -> None:
import httpcore
verify=False # TODO 강제 False설정
proxy = Proxy(url=proxy) if isinstance(proxy, (str, URL)) else proxy
ssl_context = create_ssl_context(verify=verify, cert=cert, trust_env=trust_env)
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 생략 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
반응형